Nethermind Optimism Plugin Integration Diff
diff:
ignored:
+3784
-0
+0
-38
This diff shows the changes made to integrate the Optimism plugin into Nethermind.
The base branch without the Optimism plugin can be found at github.com/NethermindEth/nethermind/tree/no-op-plugin.
The master branch with the Optimism plugin can be found at github.com/NethermindEth/nethermind.
Optimism Plugin
+3487
-0
This section shows the addition of the Nethermind.Optimism directory and its contents.
(new)
+25
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Create2DeployerContractRewriter.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Create2DeployerContractRewriter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..96cf2f65e427685c66d8a35a89c5d50936acda98
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Create2DeployerContractRewriter.cs
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Blockchain.Find;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class Create2DeployerContractRewriter(IOptimismSpecHelper opSpecHelper, ISpecProvider specProvider, IBlockTree blockTree)
+{
+ public void RewriteContract(BlockHeader header, IWorldState worldState)
+ {
+ IReleaseSpec spec = specProvider.GetSpec(header);
+ BlockHeader? parent = blockTree.FindParent(header, BlockTreeLookupOptions.None)?.Header;
+
+ // A migration at the first block of Canyon unless it's genesis
+ if ((parent is null || !opSpecHelper.IsCanyon(parent)) && opSpecHelper.IsCanyon(header) && !header.IsGenesis)
+ {
+ worldState.InsertCode(opSpecHelper.Create2DeployerAddress!, opSpecHelper.Create2DeployerCode, spec);
+ }
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/DepositTxExtensions.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/DepositTxExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5c908bb730d1779fc25944ae9d80e0746e9b2e60
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/DepositTxExtensions.cs
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+
+namespace Nethermind.Optimism;
+
+public static class DepositTxExtensions
+{
+ public static bool IsDeposit(this Transaction tx)
+ {
+ return tx.Type == TxType.DepositTx;
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IL1CostHelper.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IL1CostHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9342c78fe4f60036d2e9fbe752738fe0c8f197d0
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IL1CostHelper.cs
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+using Nethermind.Int256;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public interface IL1CostHelper
+{
+ UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState worldState);
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismConfig.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismConfig.cs
new file mode 100644
index 0000000000000000000000000000000000000000..074f8149769184906a5ad16a9f88ff7b018d3cc7
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismConfig.cs
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Config;
+
+namespace Nethermind.Optimism;
+
+public interface IOptimismConfig : IConfig
+{
+ [ConfigItem(Description = "The sequencer address.", DefaultValue = "null")]
+ string? SequencerUrl { get; set; }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5874e01fa86983bc59c54a63bc50bbd166327608
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+
+namespace Nethermind.Optimism;
+
+public interface IOptimismSpecHelper
+{
+ Address L1FeeReceiver { get; }
+
+ bool IsBedrock(BlockHeader header);
+ bool IsRegolith(BlockHeader header);
+ bool IsCanyon(BlockHeader header);
+ bool IsEcotone(BlockHeader header);
+ bool IsFjord(BlockHeader header);
+ Address? Create2DeployerAddress { get; }
+ byte[]? Create2DeployerCode { get; }
+}
(new)
+68
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockProducerOptimism.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockProducerOptimism.cs
new file mode 100644
index 0000000000000000000000000000000000000000..06111a6445e5b74a67475c5a68d6e071c8e5b47b
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockProducerOptimism.cs
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Api;
+using Nethermind.Config;
+using Nethermind.Consensus;
+using Nethermind.Core;
+using Nethermind.Init.Steps;
+
+namespace Nethermind.Optimism;
+
+public class InitializeBlockProducerOptimism : InitializeBlockProducer
+{
+ private readonly OptimismNethermindApi _api;
+
+ public InitializeBlockProducerOptimism(OptimismNethermindApi api) : base(api)
+ {
+ _api = api;
+ }
+
+ protected override IBlockProducer BuildProducer()
+ {
+ if (_api.DbProvider is null) throw new StepDependencyException(nameof(_api.DbProvider));
+ if (_api.BlockTree is null) throw new StepDependencyException(nameof(_api.BlockTree));
+ if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider));
+ if (_api.RewardCalculatorSource is null) throw new StepDependencyException(nameof(_api.RewardCalculatorSource));
+ if (_api.ReceiptStorage is null) throw new StepDependencyException(nameof(_api.ReceiptStorage));
+ if (_api.TxPool is null) throw new StepDependencyException(nameof(_api.TxPool));
+ if (_api.TransactionComparerProvider is null) throw new StepDependencyException(nameof(_api.TransactionComparerProvider));
+ if (_api.BlockValidator is null) throw new StepDependencyException(nameof(_api.BlockValidator));
+ if (_api.SpecHelper is null) throw new StepDependencyException(nameof(_api.SpecHelper));
+ if (_api.L1CostHelper is null) throw new StepDependencyException(nameof(_api.L1CostHelper));
+ if (_api.WorldStateManager is null) throw new StepDependencyException(nameof(_api.WorldStateManager));
+
+ _api.BlockProducerEnvFactory = new OptimismBlockProducerEnvFactory(
+ _api.WorldStateManager,
+ _api.BlockTree,
+ _api.SpecProvider,
+ _api.BlockValidator,
+ _api.RewardCalculatorSource,
+ _api.ReceiptStorage,
+ _api.BlockPreprocessor,
+ _api.TxPool,
+ _api.TransactionComparerProvider,
+ _api.Config<IBlocksConfig>(),
+ _api.SpecHelper,
+ _api.L1CostHelper,
+ _api.LogManager);
+
+ _api.GasLimitCalculator = new OptimismGasLimitCalculator();
+ BlockProducerEnv producerEnv = _api.BlockProducerEnvFactory.Create();
+
+ _api.BlockProducer = new OptimismPostMergeBlockProducer(
+ new OptimismPayloadTxSource(),
+ producerEnv.TxSource,
+ producerEnv.ChainProcessor,
+ producerEnv.BlockTree,
+ producerEnv.ReadOnlyStateProvider,
+ _api.GasLimitCalculator,
+ NullSealEngine.Instance,
+ new ManualTimestamper(),
+ _api.SpecProvider,
+ _api.LogManager,
+ _api.Config<IBlocksConfig>());
+
+ return _api.BlockProducer;
+ }
+}
(new)
+110
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs
new file mode 100644
index 0000000000000000000000000000000000000000..561bc844a9e0d7cea3ff6874ba7d125e33a192e7
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Api;
+using Nethermind.Blockchain.BeaconBlockRoot;
+using Nethermind.Blockchain.Blocks;
+using Nethermind.Blockchain.Services;
+using Nethermind.Config;
+using Nethermind.Consensus.Processing;
+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;
+using Nethermind.Logging;
+using Nethermind.Merge.Plugin.InvalidChainTracker;
+
+namespace Nethermind.Optimism;
+
+public class InitializeBlockchainOptimism(OptimismNethermindApi api) : InitializeBlockchain(api)
+{
+ private readonly IBlocksConfig _blocksConfig = api.Config<IBlocksConfig>();
+
+ protected override Task InitBlockchain()
+ {
+ api.RegisterTxType(TxType.DepositTx, new OptimismTxDecoder<Transaction>(), 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));
+
+ return new OptimismTransactionProcessor(
+ api.SpecProvider,
+ api.WorldState,
+ virtualMachine,
+ api.LogManager,
+ api.L1CostHelper,
+ api.SpecHelper,
+ codeInfoRepository
+ );
+ }
+
+ protected override IHeaderValidator CreateHeaderValidator()
+ {
+ if (api.InvalidChainTracker is null) throw new StepDependencyException(nameof(api.InvalidChainTracker));
+
+ OptimismHeaderValidator opHeaderValidator = new(
+ api.BlockTree,
+ api.SealValidator,
+ api.SpecProvider,
+ 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));
+ return new InvalidBlockInterceptor(base.CreateBlockValidator(), api.InvalidChainTracker, api.LogManager);
+ }
+
+ protected override BlockProcessor CreateBlockProcessor(BlockCachePreWarmer? preWarmer)
+ {
+ 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);
+
+ return new OptimismBlockProcessor(
+ api.SpecProvider,
+ api.BlockValidator,
+ api.RewardCalculatorSource.Get(transactionProcessor),
+ new BlockProcessor.BlockValidationTransactionsExecutor(transactionProcessor, api.WorldState),
+ api.WorldState,
+ api.ReceiptStorage,
+ transactionProcessor,
+ new BlockhashStore(api.SpecProvider, api.WorldState),
+ new BeaconBlockRootHandler(transactionProcessor),
+ api.LogManager,
+ api.SpecHelper,
+ contractRewriter,
+ new BlockProductionWithdrawalProcessor(new NullWithdrawalProcessor()),
+ preWarmer: preWarmer);
+ }
+
+ protected override IUnclesValidator CreateUnclesValidator() => Always.Valid;
+
+ protected override IHealthHintService CreateHealthHintService() =>
+ new ManualHealthHintService(_blocksConfig.SecondsPerSlot * 6, HealthHintConstants.InfinityHint);
+
+ protected override IBlockProductionPolicy CreateBlockProductionPolicy() => AlwaysStartBlockProductionPolicy.Instance;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/L1BlockGasInfo.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/L1BlockGasInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0df4477eb405f3985985015063052ea7b833f639
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/L1BlockGasInfo.cs
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+using Nethermind.Int256;
+using System;
+using System.Linq;
+
+namespace Nethermind.Optimism;
+
+public readonly struct L1TxGasInfo(UInt256? l1Fee, UInt256? l1GasPrice, UInt256? l1GasUsed, string? l1FeeScalar, UInt256? l1BaseFeeScalar = null, UInt256? l1BlobBaseFee = null, UInt256? l1BlobBaseFeeScalar = null)
+{
+ public UInt256? L1Fee { get; } = l1Fee;
+ public UInt256? L1GasPrice { get; } = l1GasPrice;
+ public UInt256? L1GasUsed { get; } = l1GasUsed;
+ public string? L1FeeScalar { get; } = l1FeeScalar;
+
+ public UInt256? L1BaseFeeScalar { get; } = l1BaseFeeScalar;
+ public UInt256? L1BlobBaseFee { get; } = l1BlobBaseFee;
+ public UInt256? L1BlobBaseFeeScalar { get; } = l1BlobBaseFeeScalar;
+}
+
+public readonly struct L1BlockGasInfo
+{
+ private readonly UInt256? _l1GasPrice;
+ private readonly UInt256? _l1BlobBaseFee;
+ private readonly UInt256? _l1BaseFeeScalar;
+ private readonly UInt256? _l1BlobBaseFeeScalar;
+ private readonly UInt256 _l1BaseFee;
+ private readonly UInt256 _overhead;
+ private readonly UInt256 _feeScalar;
+ private readonly string? _feeScalarDecimal;
+ private readonly bool _isFjord;
+ private readonly bool _isEcotone;
+ private readonly bool _isPostRegolith;
+
+ private static readonly byte[] BedrockL1AttributesSelector = [0x01, 0x5d, 0x8e, 0xb9];
+ private readonly IOptimismSpecHelper _specHelper;
+
+ public L1BlockGasInfo(Block block, IOptimismSpecHelper specHelper)
+ {
+ _specHelper = specHelper;
+
+ if (block is not null && block.Transactions.Length > 0)
+ {
+ Transaction depositTx = block.Transactions[0];
+ if (depositTx.Data is null || depositTx.Data.Value.Length < 4)
+ {
+ return;
+ }
+
+ Memory<byte> data = depositTx.Data.Value;
+
+ _isFjord = _specHelper.IsFjord(block.Header);
+ _isPostRegolith = _specHelper.IsRegolith(block.Header);
+
+ if (_isFjord || (_isEcotone = (_specHelper.IsEcotone(block.Header) && !data[0..4].Span.SequenceEqual(BedrockL1AttributesSelector))))
+ {
+ if (data.Length != 164)
+ {
+ return;
+ }
+
+ _l1GasPrice = new(data[36..68].Span, true);
+ _l1BlobBaseFee = new(data[68..100].Span, true);
+ _l1BaseFeeScalar = new(data[4..8].Span, true);
+ _l1BlobBaseFeeScalar = new(data[8..12].Span, true);
+ }
+ else
+ {
+ if (data.Length < 4 + 32 * 8)
+ {
+ return;
+ }
+
+ _l1GasPrice = new(data[(4 + 32 * 2)..(4 + 32 * 3)].Span, true);
+ _l1BaseFee = new(data[(4 + 32 * 2)..(4 + 32 * 3)].Span, true);
+ _overhead = new(data[(4 + 32 * 6)..(4 + 32 * 7)].Span, true);
+ _feeScalar = new UInt256(data[(4 + 32 * 7)..(4 + 32 * 8)].Span, true);
+ _feeScalarDecimal = (((ulong)_feeScalar) / 1_000_000m).ToString();
+ }
+ }
+ }
+
+ public readonly L1TxGasInfo GetTxGasInfo(Transaction tx)
+ {
+ UInt256? l1Fee = null;
+ UInt256? l1GasUsed = null;
+
+ if (_l1GasPrice is not null)
+ {
+ if (_isFjord)
+ {
+ UInt256 fastLzSize = OPL1CostHelper.ComputeFlzCompressLen(tx);
+ l1Fee = OPL1CostHelper.ComputeL1CostFjord(fastLzSize, _l1GasPrice.Value, _l1BlobBaseFee!.Value, _l1BaseFeeScalar!.Value, _l1BlobBaseFeeScalar!.Value, out UInt256 estimatedSize);
+ l1GasUsed = OPL1CostHelper.ComputeGasUsedFjord(estimatedSize);
+ }
+ else if (_isEcotone)
+ {
+ l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isPostRegolith);
+ l1Fee = OPL1CostHelper.ComputeL1CostEcotone(l1GasUsed.Value, _l1GasPrice.Value, _l1BlobBaseFee!.Value, _l1BaseFeeScalar!.Value, _l1BlobBaseFeeScalar!.Value);
+ }
+ else
+ {
+ l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isPostRegolith) + _overhead;
+ l1Fee = OPL1CostHelper.ComputeL1CostPreEcotone(l1GasUsed.Value, _l1BaseFee, _feeScalar);
+ }
+ }
+
+ return new L1TxGasInfo(l1Fee, _l1GasPrice, l1GasUsed, _feeScalarDecimal, _l1BaseFeeScalar, _l1BlobBaseFee, _l1BlobBaseFeeScalar);
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Nethermind.Optimism.csproj NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Nethermind.Optimism.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..939e7284adbc65919d91ee28d0be33597225b4ce
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Nethermind.Optimism.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Nethermind.Api\Nethermind.Api.csproj" />
+ <ProjectReference Include="..\Nethermind.Blockchain\Nethermind.Blockchain.csproj" />
+ <ProjectReference Include="..\Nethermind.Core\Nethermind.Core.csproj" />
+ <ProjectReference Include="..\Nethermind.Consensus\Nethermind.Consensus.csproj" />
+ <ProjectReference Include="..\Nethermind.Init\Nethermind.Init.csproj" />
+ <ProjectReference Include="..\Nethermind.JsonRpc\Nethermind.JsonRpc.csproj" />
+ <ProjectReference Include="..\Nethermind.Merge.Plugin\Nethermind.Merge.Plugin.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c404456c8aa89714bb18d3525f5c032c468f6839
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+using Nethermind.Specs.ChainSpecStyle;
+
+namespace Nethermind.Optimism;
+
+public class OptimismSpecHelper(OptimismParameters parameters) : IOptimismSpecHelper
+{
+ private readonly long _bedrockBlockNumber = parameters.BedrockBlockNumber;
+ private readonly ulong _regolithTimestamp = parameters.RegolithTimestamp;
+ private readonly ulong? _canyonTimestamp = parameters.CanyonTimestamp;
+ private readonly ulong? _ecotoneTimestamp = parameters.EcotoneTimestamp;
+ private readonly ulong? _fjordTimestamp = parameters.FjordTimestamp;
+
+ public Address L1FeeReceiver { get; init; } = parameters.L1FeeRecipient;
+
+ public bool IsRegolith(BlockHeader header)
+ {
+ return header.Timestamp >= _regolithTimestamp;
+ }
+
+ public bool IsBedrock(BlockHeader header)
+ {
+ return header.Number >= _bedrockBlockNumber;
+ }
+
+ public bool IsCanyon(BlockHeader header)
+ {
+ return header.Timestamp >= _canyonTimestamp;
+ }
+
+ public bool IsEcotone(BlockHeader header)
+ {
+ return header.Timestamp >= _ecotoneTimestamp;
+ }
+
+ public bool IsFjord(BlockHeader header)
+ {
+ return header.Timestamp >= _fjordTimestamp;
+ }
+
+ public Address? Create2DeployerAddress { get; } = parameters.Create2DeployerAddress;
+ public byte[]? Create2DeployerCode { get; } = parameters.Create2DeployerCode;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPL1CostHelper.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPL1CostHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1cf69cf5ee914dad148f72f5db207bceb8380c34
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OPL1CostHelper.cs
@@ -0,0 +1,246 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Nethermind.Core;
+using Nethermind.Evm;
+using Nethermind.Int256;
+using Nethermind.Serialization.Rlp;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class OPL1CostHelper(IOptimismSpecHelper opSpecHelper, Address l1BlockAddr) : IL1CostHelper
+{
+ private readonly IOptimismSpecHelper _opSpecHelper = opSpecHelper;
+
+ private readonly StorageCell _l1BaseFeeSlot = new(l1BlockAddr, new UInt256(1));
+ private readonly StorageCell _overheadSlot = new(l1BlockAddr, new UInt256(5));
+ private readonly StorageCell _scalarSlot = new(l1BlockAddr, new UInt256(6));
+
+ private static readonly UInt256 BasicDivisor = 1_000_000;
+
+ // Ecotone
+ private readonly StorageCell _blobBaseFeeSlot = new(l1BlockAddr, new UInt256(7));
+ private readonly StorageCell _baseFeeScalarSlot = new(l1BlockAddr, new UInt256(3));
+
+ private static readonly UInt256 PrecisionMultiplier = 16;
+ private static readonly UInt256 PrecisionDivisor = PrecisionMultiplier * BasicDivisor;
+
+
+ // Fjord
+ private static readonly UInt256 L1CostInterceptNeg = 42_585_600;
+ private static readonly UInt256 L1CostFastlzCoef = 836_500;
+
+ private static readonly UInt256 MinTransactionSizeScaled = 100 * 1_000_000;
+ private static readonly UInt256 FjordDivisor = 1_000_000_000_000;
+
+ [SkipLocalsInit]
+ public UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState worldState)
+ {
+ if (tx.IsDeposit())
+ return UInt256.Zero;
+
+ UInt256 l1BaseFee = new(worldState.Get(_l1BaseFeeSlot), true);
+
+ if (_opSpecHelper.IsFjord(header))
+ {
+ UInt256 blobBaseFee = new(worldState.Get(_blobBaseFeeSlot), true);
+
+ ReadOnlySpan<byte> scalarData = worldState.Get(_baseFeeScalarSlot);
+
+ const int baseFeeFieldsStart = 16;
+ const int fieldSize = sizeof(uint);
+
+ int l1BaseFeeScalarStart = scalarData.Length > baseFeeFieldsStart ? scalarData.Length - baseFeeFieldsStart : 0;
+ int l1BaseFeeScalarEnd = l1BaseFeeScalarStart + (scalarData.Length >= baseFeeFieldsStart ? fieldSize : fieldSize - baseFeeFieldsStart + scalarData.Length);
+ UInt256 l1BaseFeeScalar = new(scalarData[l1BaseFeeScalarStart..l1BaseFeeScalarEnd], true);
+ UInt256 l1BlobBaseFeeScalar = new(scalarData[l1BaseFeeScalarEnd..(l1BaseFeeScalarEnd + fieldSize)], true);
+
+ uint fastLzSize = ComputeFlzCompressLen(tx);
+
+ return ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar, out _);
+ }
+
+ UInt256 dataGas = ComputeDataGas(tx, _opSpecHelper.IsRegolith(header));
+
+ if (dataGas.IsZero)
+ return UInt256.Zero;
+
+ if (_opSpecHelper.IsEcotone(header))
+ {
+ UInt256 blobBaseFee = new(worldState.Get(_blobBaseFeeSlot), true);
+
+ ReadOnlySpan<byte> scalarData = worldState.Get(_baseFeeScalarSlot);
+
+ const int baseFeeFieldsStart = 16;
+ const int fieldSize = sizeof(uint);
+
+ int l1BaseFeeScalarStart = scalarData.Length > baseFeeFieldsStart ? scalarData.Length - baseFeeFieldsStart : 0;
+ int l1BaseFeeScalarEnd = l1BaseFeeScalarStart + (scalarData.Length >= baseFeeFieldsStart ? fieldSize : fieldSize - baseFeeFieldsStart + scalarData.Length);
+ UInt256 l1BaseFeeScalar = new(scalarData[l1BaseFeeScalarStart..l1BaseFeeScalarEnd], true);
+ UInt256 l1BlobBaseFeeScalar = new(scalarData[l1BaseFeeScalarEnd..(l1BaseFeeScalarEnd + fieldSize)], true);
+
+ return ComputeL1CostEcotone(dataGas, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar);
+ }
+ else
+ {
+ UInt256 overhead = new(worldState.Get(_overheadSlot), true);
+ UInt256 feeScalar = new(worldState.Get(_scalarSlot), true);
+
+ return ComputeL1CostPreEcotone(dataGas + overhead, l1BaseFee, feeScalar);
+ }
+ }
+
+ [SkipLocalsInit]
+ public static UInt256 ComputeDataGas(Transaction tx, bool isRegolith)
+ {
+ byte[] encoded = Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes;
+
+ long zeroCount = encoded.Count(b => b == 0);
+ long nonZeroCount = encoded.Length - zeroCount;
+ // Add pre-EIP-3529 overhead
+ nonZeroCount += isRegolith ? 0 : OptimismConstants.PreRegolithNonZeroCountOverhead;
+
+ return (ulong)(zeroCount * GasCostOf.TxDataZero + nonZeroCount * GasCostOf.TxDataNonZeroEip2028);
+ }
+
+ // Fjord L1 formula:
+ // l1FeeScaled = baseFeeScalar * l1BaseFee * 16 + blobFeeScalar * l1BlobBaseFee
+ // estimatedSize = max(minTransactionSize, intercept + fastlzCoef * fastlzSize)
+ // l1Cost = estimatedSize * l1FeeScaled / 1e12
+ public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar, out UInt256 estimatedSize)
+ {
+ UInt256 l1FeeScaled = l1BaseFeeScalar * l1BaseFee * PrecisionMultiplier + l1BlobBaseFeeScalar * blobBaseFee;
+ UInt256 fastLzCost = L1CostFastlzCoef * fastLzSize;
+
+ if (fastLzCost < L1CostInterceptNeg)
+ {
+ fastLzCost = 0;
+ }
+ else
+ {
+ fastLzCost -= L1CostInterceptNeg;
+ }
+
+ estimatedSize = UInt256.Max(MinTransactionSizeScaled, fastLzCost);
+ return estimatedSize * l1FeeScaled / FjordDivisor;
+ }
+
+ // Ecotone formula: (dataGas) * (16 * l1BaseFee * l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar) / 16e6
+ public static UInt256 ComputeL1CostEcotone(UInt256 dataGas, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar)
+ {
+ return dataGas * (PrecisionMultiplier * l1BaseFee * l1BaseFeeScalar + blobBaseFee * l1BlobBaseFeeScalar) / PrecisionDivisor;
+ }
+
+ // Pre-Ecotone formula: (dataGas + overhead) * l1BaseFee * scalar / 1e6
+ public static UInt256 ComputeL1CostPreEcotone(UInt256 dataGasWithOverhead, UInt256 l1BaseFee, UInt256 feeScalar)
+ {
+ return dataGasWithOverhead * l1BaseFee * feeScalar / BasicDivisor;
+ }
+
+ // Based on:
+ // https://github.com/ethereum-optimism/op-geth/blob/7c2819836018bfe0ca07c4e4955754834ffad4e0/core/types/rollup_cost.go
+ // https://github.com/Vectorized/solady/blob/5315d937d79b335c668896d7533ac603adac5315/js/solady.js
+ // Do not use SkipLocalsInit, `ht` should be zeros initially
+ public static uint ComputeFlzCompressLen(Transaction tx)
+ {
+ byte[] encoded = Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes;
+
+ static uint FlzCompressLen(byte[] data)
+ {
+ uint n = 0;
+ Span<uint> ht = stackalloc uint[8192];
+
+ uint u24(uint i) => data[i] | ((uint)data[i + 1] << 8) | ((uint)data[i + 2] << 16);
+ uint cmp(uint p, uint q, uint e)
+ {
+ uint l = 0;
+ for (e -= q; l < e; l++)
+ {
+ if (data[p + (int)l] != data[q + (int)l])
+ {
+ e = 0;
+ }
+ }
+ return l;
+ }
+ void literals(uint r)
+ {
+ n += 0x21 * (r / 0x20);
+ r %= 0x20;
+ if (r != 0)
+ {
+ n += r + 1;
+ }
+ }
+ void match(uint l)
+ {
+ l--;
+ n += 3 * (l / 262);
+ if (l % 262 >= 6)
+ {
+ n += 3;
+ }
+ else
+ {
+ n += 2;
+ }
+ }
+ uint hash(uint v) => ((2654435769 * v) >> 19) & 0x1fff;
+ uint setNextHash(uint ip, ref Span<uint> ht)
+ {
+ ht[(int)hash(u24(ip))] = ip;
+ return ip + 1;
+ }
+ uint a = 0;
+ uint ipLimit = (uint)data.Length - 13;
+ if (data.Length < 13)
+ {
+ ipLimit = 0;
+ }
+ for (uint ip = a + 2; ip < ipLimit;)
+ {
+ uint d;
+ uint r;
+ for (; ; )
+ {
+ uint s = u24(ip);
+ int h = (int)hash(s);
+ r = ht[h];
+ ht[h] = ip;
+ d = ip - r;
+ if (ip >= ipLimit)
+ {
+ break;
+ }
+ ip++;
+ if (d <= 0x1fff && s == u24(r))
+ {
+ break;
+ }
+ }
+ if (ip >= ipLimit)
+ {
+ break;
+ }
+ ip--;
+ if (ip > a)
+ {
+ literals(ip - a);
+ }
+ uint l = cmp(r + 3, ip + 3, ipLimit + 9);
+ match(l);
+ ip = setNextHash(setNextHash(ip + l, ref ht), ref ht);
+ a = ip;
+ }
+ literals((uint)data.Length - a);
+ return n;
+ }
+ return FlzCompressLen(encoded);
+ }
+
+ internal static UInt256 ComputeGasUsedFjord(UInt256 estimatedSize) => estimatedSize * GasCostOf.TxDataNonZeroEip2028 / BasicDivisor;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0c87d77e2a9bb7215bc5117b238911f0e9309408
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Blockchain.BeaconBlockRoot;
+using Nethermind.Blockchain.Blocks;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Consensus.Validators;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.Tracing;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class OptimismBlockProcessor : BlockProcessor
+{
+ private readonly Create2DeployerContractRewriter? _contractRewriter;
+
+ public OptimismBlockProcessor(
+ ISpecProvider? specProvider,
+ IBlockValidator? blockValidator,
+ IRewardCalculator? rewardCalculator,
+ IBlockProcessor.IBlockTransactionsExecutor? blockTransactionsExecutor,
+ IWorldState? stateProvider,
+ IReceiptStorage? receiptStorage,
+ ITransactionProcessor transactionProcessor,
+ IBlockhashStore? blockhashStore,
+ IBeaconBlockRootHandler? beaconBlockRootHandler,
+ ILogManager? logManager,
+ IOptimismSpecHelper opSpecHelper,
+ Create2DeployerContractRewriter contractRewriter,
+ IWithdrawalProcessor? withdrawalProcessor = null,
+ IBlockCachePreWarmer? preWarmer = null)
+ : base(
+ specProvider,
+ blockValidator,
+ rewardCalculator,
+ blockTransactionsExecutor,
+ stateProvider,
+ receiptStorage,
+ transactionProcessor,
+ beaconBlockRootHandler,
+ blockhashStore,
+ logManager,
+ withdrawalProcessor,
+ ReceiptsRootCalculator.Instance,
+ preWarmer)
+ {
+ ArgumentNullException.ThrowIfNull(stateProvider);
+ _contractRewriter = contractRewriter;
+ ReceiptsTracer = new OptimismBlockReceiptTracer(opSpecHelper, stateProvider);
+ }
+
+ protected override TxReceipt[] ProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options)
+ {
+ _contractRewriter?.RewriteContract(block.Header, _stateProvider);
+ return base.ProcessBlock(block, blockTracer, options);
+ }
+}
(new)
+98
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProducerEnvFactory.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProducerEnvFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..88ee38dd5e3728381047ddc6f390072a7800be4a
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProducerEnvFactory.cs
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Blockchain.BeaconBlockRoot;
+using Nethermind.Blockchain.Blocks;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Config;
+using Nethermind.Consensus.Comparers;
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Producers;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Consensus.Transactions;
+using Nethermind.Consensus.Validators;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+using Nethermind.State;
+using Nethermind.TxPool;
+
+namespace Nethermind.Optimism;
+
+public class OptimismBlockProducerEnvFactory : BlockProducerEnvFactory
+{
+ private readonly OptimismSpecHelper _specHelper;
+ private readonly OPL1CostHelper _l1CostHelper;
+
+ public OptimismBlockProducerEnvFactory(
+ IWorldStateManager worldStateManager,
+ IBlockTree blockTree,
+ ISpecProvider specProvider,
+ IBlockValidator blockValidator,
+ IRewardCalculatorSource rewardCalculatorSource,
+ IReceiptStorage receiptStorage,
+ IBlockPreprocessorStep blockPreprocessorStep,
+ ITxPool txPool,
+ ITransactionComparerProvider transactionComparerProvider,
+ IBlocksConfig blocksConfig,
+ OptimismSpecHelper specHelper,
+ OPL1CostHelper l1CostHelper,
+ ILogManager logManager) : base(
+ worldStateManager,
+ blockTree,
+ specProvider,
+ blockValidator,
+ rewardCalculatorSource,
+ receiptStorage,
+ blockPreprocessorStep,
+ txPool,
+ transactionComparerProvider,
+ blocksConfig,
+ logManager)
+ {
+ _specHelper = specHelper;
+ _l1CostHelper = l1CostHelper;
+ TransactionsExecutorFactory = new OptimismTransactionsExecutorFactory(specProvider, logManager);
+ }
+
+ protected override ReadOnlyTxProcessingEnv CreateReadonlyTxProcessingEnv(IWorldStateManager worldStateManager,
+ ReadOnlyBlockTree readOnlyBlockTree) =>
+ new OptimismReadOnlyTxProcessingEnv(worldStateManager, readOnlyBlockTree, _specProvider, _logManager, _l1CostHelper, _specHelper);
+
+ protected override ITxSource CreateTxSourceForProducer(ITxSource? additionalTxSource,
+ ReadOnlyTxProcessingEnv processingEnv,
+ ITxPool txPool, IBlocksConfig blocksConfig, ITransactionComparerProvider transactionComparerProvider,
+ ILogManager logManager)
+ {
+ ITxSource baseTxSource = base.CreateTxSourceForProducer(additionalTxSource, processingEnv, txPool, blocksConfig,
+ transactionComparerProvider, logManager);
+
+ return new OptimismTxPoolTxSource(baseTxSource);
+ }
+
+ protected override BlockProcessor CreateBlockProcessor(
+ IReadOnlyTxProcessingScope readOnlyTxProcessingEnv,
+ ISpecProvider specProvider,
+ IBlockValidator blockValidator,
+ IRewardCalculatorSource rewardCalculatorSource,
+ IReceiptStorage receiptStorage,
+ ILogManager logManager,
+ IBlocksConfig blocksConfig)
+ {
+ return new OptimismBlockProcessor(specProvider,
+ blockValidator,
+ rewardCalculatorSource.Get(readOnlyTxProcessingEnv.TransactionProcessor),
+ TransactionsExecutorFactory.Create(readOnlyTxProcessingEnv),
+ readOnlyTxProcessingEnv.WorldState,
+ receiptStorage,
+ readOnlyTxProcessingEnv.TransactionProcessor,
+ new BlockhashStore(specProvider, readOnlyTxProcessingEnv.WorldState),
+ new BeaconBlockRootHandler(readOnlyTxProcessingEnv.TransactionProcessor),
+ logManager,
+ _specHelper,
+ new Create2DeployerContractRewriter(_specHelper, _specProvider, _blockTree),
+ new BlockProductionWithdrawalProcessor(new WithdrawalProcessor(readOnlyTxProcessingEnv.WorldState, logManager)));
+ }
+}
(new)
+29
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProductionTransactionPicker.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProductionTransactionPicker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5480936a2f5c0d412babb3f44f384b6bdd5ddd38
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockProductionTransactionPicker.cs
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections.Generic;
+using Nethermind.Consensus.Processing;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class OptimismBlockProductionTransactionPicker : BlockProcessor.BlockProductionTransactionPicker
+{
+ public OptimismBlockProductionTransactionPicker(ISpecProvider specProvider) : base(specProvider)
+ {
+ }
+
+ public override BlockProcessor.AddingTxEventArgs CanAddTransaction(Block block, Transaction currentTx,
+ IReadOnlySet<Transaction> transactionsInBlock, IWorldState stateProvider)
+ {
+ if (!currentTx.IsDeposit())
+ return base.CanAddTransaction(block, currentTx, transactionsInBlock, stateProvider);
+
+ // Trusting CL with deposit tx validation
+ BlockProcessor.AddingTxEventArgs args = new(transactionsInBlock.Count, currentTx, block, transactionsInBlock);
+ OnAddingTransaction(args);
+ return args;
+ }
+}
(new)
+74
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..208d02a35e10957f686b9a7b2c5e0c26e8162c5a
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Evm.Tracing;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class OptimismBlockReceiptTracer : BlockReceiptsTracer
+{
+ private readonly IOptimismSpecHelper _opSpecHelper;
+ private readonly IWorldState _worldState;
+
+ public OptimismBlockReceiptTracer(IOptimismSpecHelper opSpecHelper, IWorldState worldState)
+ {
+ _opSpecHelper = opSpecHelper;
+ _worldState = worldState;
+ }
+
+ private (ulong?, ulong?) GetDepositReceiptData(BlockHeader header)
+ {
+ ArgumentNullException.ThrowIfNull(CurrentTx);
+
+ ulong? depositNonce = null;
+ ulong? version = null;
+
+ if (CurrentTx.IsDeposit())
+ {
+ depositNonce = _worldState.GetNonce(CurrentTx.SenderAddress!).ToUInt64(null);
+ // We write nonce after tx processing, so need to subtract one
+ if (depositNonce > 0)
+ {
+ depositNonce--;
+ }
+ if (_opSpecHelper.IsCanyon(header))
+ {
+ version = 1;
+ }
+ }
+
+ return (depositNonce, version);
+ }
+
+ protected override TxReceipt BuildReceipt(Address recipient, long spentGas, byte statusCode, LogEntry[] logEntries, Hash256? stateRoot)
+ {
+ (ulong? depositNonce, ulong? version) = GetDepositReceiptData(Block.Header);
+
+ Transaction transaction = CurrentTx!;
+ OptimismTxReceipt txReceipt = new()
+ {
+ Logs = logEntries,
+ TxType = transaction.Type,
+ // Bloom calculated in parallel with other receipts
+ GasUsedTotal = Block.GasUsed,
+ StatusCode = statusCode,
+ Recipient = transaction.IsContractCreation ? null : recipient,
+ BlockHash = Block.Hash,
+ BlockNumber = Block.Number,
+ Index = _currentIndex,
+ GasUsed = spentGas,
+ Sender = transaction.SenderAddress,
+ ContractAddress = transaction.IsContractCreation ? recipient : null,
+ TxHash = transaction.Hash,
+ PostTransactionState = stateRoot,
+ DepositNonce = depositNonce,
+ DepositReceiptVersion = version
+ };
+
+ return txReceipt;
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConfig.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConfig.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0903acfb3ce6e6f5f4f8be1c41e9d7f7138a0816
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConfig.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Nethermind.Optimism;
+
+public class OptimismConfig : IOptimismConfig
+{
+ public string? SequencerUrl { get; set; } = null;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConstants.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConstants.cs
new file mode 100644
index 0000000000000000000000000000000000000000..81ea8f1c48a944df3aa29cfd233a2ef431739f20
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismConstants.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Nethermind.Optimism;
+
+public class OptimismConstants
+{
+ public const long PreRegolithNonZeroCountOverhead = 68;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f70db353bf7c418ee615e414e23c8675790ed46c
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Crypto;
+
+namespace Nethermind.Optimism;
+
+public class OptimismEthereumEcdsa : Ecdsa, IEthereumEcdsa
+{
+ private readonly IEthereumEcdsa _ethereumEcdsa;
+
+ public OptimismEthereumEcdsa(IEthereumEcdsa ethereumEcdsa)
+ {
+ _ethereumEcdsa = ethereumEcdsa;
+ }
+
+ public void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true) => _ethereumEcdsa.Sign(privateKey, tx, isEip155Enabled);
+
+ public Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false)
+ {
+ if (tx.Signature is null && tx.IsOPSystemTransaction)
+ {
+ return Address.Zero;
+ }
+ return _ethereumEcdsa.RecoverAddress(tx, useSignatureChainId);
+ }
+
+ public Address? RecoverAddress(Signature signature, Hash256 message) => _ethereumEcdsa.RecoverAddress(signature, message);
+
+ public Address? RecoverAddress(Span<byte> signatureBytes, Hash256 message) => _ethereumEcdsa.RecoverAddress(signatureBytes, message);
+
+ public bool Verify(Address sender, Transaction tx) => _ethereumEcdsa.Verify(sender, tx);
+}
(new)
+14
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5e5a6932e95f1c038f53dd1e1289a5f9c62eb04a
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Consensus;
+using Nethermind.Core;
+
+namespace Nethermind.Optimism;
+
+public class OptimismGasLimitCalculator : IGasLimitCalculator
+{
+ public long GetGasLimit(BlockHeader parentHeader) =>
+ throw new InvalidOperationException("GasLimit in Optimism should come from payload attributes.");
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs
new file mode 100644
index 0000000000000000000000000000000000000000..32db335b5729dbdfd6c4a68353863f373dbf7553
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Consensus;
+using Nethermind.Consensus.Validators;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.Logging;
+
+namespace Nethermind.Optimism;
+
+public class OptimismHeaderValidator(
+ IBlockTree? blockTree,
+ ISealValidator? sealValidator,
+ ISpecProvider? specProvider,
+ ILogManager? logManager)
+ : HeaderValidator(blockTree, sealValidator, specProvider, logManager)
+{
+ protected override bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) => true;
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismNethermindApi.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismNethermindApi.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8e1e8f4002d25c84702293522ddcc6358ab96249
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismNethermindApi.cs
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Api;
+using Nethermind.Config;
+using Nethermind.Logging;
+using Nethermind.Merge.Plugin.InvalidChainTracker;
+using Nethermind.Serialization.Json;
+using Nethermind.Specs.ChainSpecStyle;
+
+namespace Nethermind.Optimism;
+
+public class OptimismNethermindApi : NethermindApi
+{
+ public OptimismNethermindApi(
+ IConfigProvider configProvider,
+ IJsonSerializer jsonSerializer,
+ ILogManager logManager,
+ ChainSpec chainSpec) : base(configProvider, jsonSerializer, logManager, chainSpec)
+ {
+ }
+
+ public IInvalidChainTracker? InvalidChainTracker { get; set; }
+ public OPL1CostHelper? L1CostHelper { get; set; }
+ public OptimismSpecHelper? SpecHelper { get; set; }
+}
(new)
+55
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..35fd2e3abb29ca350099f2eb3533c78313b3315f
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Consensus.Producers;
+using Nethermind.Core;
+using Nethermind.Core.Timers;
+using Nethermind.Int256;
+using Nethermind.Logging;
+using Nethermind.Merge.Plugin.BlockProduction;
+using Nethermind.Optimism.Rpc;
+
+namespace Nethermind.Optimism;
+
+public class OptimismPayloadPreparationService : PayloadPreparationService
+{
+ private readonly ILogger _logger;
+
+ public OptimismPayloadPreparationService(
+ PostMergeBlockProducer blockProducer,
+ IBlockImprovementContextFactory blockImprovementContextFactory,
+ ITimerFactory timerFactory,
+ ILogManager logManager,
+ TimeSpan timePerSlot,
+ int slotsPerOldPayloadCleanup = SlotsPerOldPayloadCleanup,
+ TimeSpan? improvementDelay = null,
+ TimeSpan? minTimeForProduction = null)
+ : base(
+ blockProducer,
+ blockImprovementContextFactory,
+ timerFactory,
+ logManager,
+ timePerSlot,
+ slotsPerOldPayloadCleanup,
+ improvementDelay,
+ minTimeForProduction)
+ {
+ _logger = logManager.GetClassLogger();
+ }
+
+ protected override void ImproveBlock(string payloadId, BlockHeader parentHeader,
+ PayloadAttributes payloadAttributes, Block currentBestBlock, DateTimeOffset startDateTime)
+ {
+ if (payloadAttributes is OptimismPayloadAttributes { NoTxPool: true })
+ {
+ if (_logger.IsDebug)
+ _logger.Debug("Skip block improvement because of NoTxPool payload attribute.");
+
+ // ignore TryAdd failure (it can only happen if payloadId is already in the dictionary)
+ _payloadStorage.TryAdd(payloadId,
+ new NoBlockImprovementContext(currentBestBlock, UInt256.Zero, startDateTime));
+ }
+ else base.ImproveBlock(payloadId, parentHeader, payloadAttributes, currentBestBlock, startDateTime);
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadTxSource.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadTxSource.cs
new file mode 100644
index 0000000000000000000000000000000000000000..53b23ea2695184f76fa20be6ecb586f9dc3542ac
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPayloadTxSource.cs
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Consensus.Producers;
+using Nethermind.Consensus.Transactions;
+using Nethermind.Core;
+using Nethermind.Optimism.Rpc;
+
+namespace Nethermind.Optimism;
+
+public class OptimismPayloadTxSource : ITxSource
+{
+ public IEnumerable<Transaction> GetTransactions(BlockHeader parent, long gasLimit, PayloadAttributes? payloadAttributes)
+ {
+ if (payloadAttributes is OptimismPayloadAttributes optimismPayloadAttributes)
+ {
+ Transaction[]? transactions = optimismPayloadAttributes.GetTransactions();
+ if (transactions is not null)
+ {
+ return transactions;
+ }
+ }
+
+ return Enumerable.Empty<Transaction>();
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0f45fe0ddbff03ed4c0f3b929ceced83c1943b52
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
@@ -0,0 +1,304 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Threading.Tasks;
+using Nethermind.Api;
+using Nethermind.Api.Extensions;
+using Nethermind.Consensus;
+using Nethermind.Consensus.Producers;
+using Nethermind.Consensus.Transactions;
+using Nethermind.Merge.Plugin;
+using Nethermind.Merge.Plugin.BlockProduction;
+using Nethermind.Merge.Plugin.GC;
+using Nethermind.Merge.Plugin.Handlers;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.Config;
+using Nethermind.Logging;
+using Nethermind.Blockchain.Synchronization;
+using Nethermind.Merge.Plugin.InvalidChainTracker;
+using Nethermind.Blockchain;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Merge.Plugin.Synchronization;
+using Nethermind.Synchronization.ParallelSync;
+using Nethermind.HealthChecks;
+using Nethermind.Serialization.Json;
+using Nethermind.Specs.ChainSpecStyle;
+using Nethermind.Serialization.Rlp;
+using Nethermind.Optimism.Rpc;
+using Nethermind.Core;
+using Nethermind.JsonRpc.Modules.Eth;
+using Nethermind.Merge.Plugin.handlers;
+
+namespace Nethermind.Optimism;
+
+public class OptimismPlugin : IConsensusPlugin, ISynchronizationPlugin, IInitializationPlugin
+{
+ public string Author => "Nethermind";
+ public string Name => "Optimism";
+ public string Description => "Optimism support for Nethermind";
+
+ private OptimismNethermindApi? _api;
+ private ILogger _logger;
+ private IMergeConfig _mergeConfig = null!;
+ private ISyncConfig _syncConfig = null!;
+ private IBlocksConfig _blocksConfig = null!;
+ private BlockCacheService? _blockCacheService;
+ private InvalidChainTracker? _invalidChainTracker;
+ private ManualBlockFinalizationManager? _blockFinalizationManager;
+ private IPeerRefresher? _peerRefresher;
+ private IBeaconPivot? _beaconPivot;
+ private BeaconSync? _beaconSync;
+
+ public bool ShouldRunSteps(INethermindApi api) => api.ChainSpec.SealEngineType == SealEngineType;
+
+ #region IConsensusPlugin
+
+ public string SealEngineType => Core.SealEngineType.Optimism;
+
+ public IBlockProductionTrigger DefaultBlockProductionTrigger => NeverProduceTrigger.Instance;
+
+ public IBlockProducer InitBlockProducer(ITxSource? additionalTxSource = null)
+ {
+ if (additionalTxSource is not null)
+ throw new ArgumentException(
+ "Optimism does not support additional tx source");
+
+ ArgumentNullException.ThrowIfNull(_api);
+ ArgumentNullException.ThrowIfNull(_api.BlockProducer);
+
+ return _api.BlockProducer;
+ }
+
+ #endregion
+
+ public INethermindApi CreateApi(IConfigProvider configProvider, IJsonSerializer jsonSerializer,
+ ILogManager logManager, ChainSpec chainSpec) =>
+ new OptimismNethermindApi(configProvider, jsonSerializer, logManager, chainSpec);
+
+ public void InitRlpDecoders(INethermindApi api)
+ {
+ if (ShouldRunSteps(api))
+ {
+ Rlp.RegisterDecoders(typeof(OptimismReceiptMessageDecoder).Assembly, true);
+ }
+ }
+
+ public Task Init(INethermindApi api)
+ {
+ if (!ShouldRunSteps(api))
+ return Task.CompletedTask;
+
+ _api = (OptimismNethermindApi)api;
+ _mergeConfig = _api.Config<IMergeConfig>();
+ _syncConfig = _api.Config<ISyncConfig>();
+ _blocksConfig = _api.Config<IBlocksConfig>();
+ _logger = _api.LogManager.GetClassLogger();
+
+ ArgumentNullException.ThrowIfNull(_api.BlockTree);
+ ArgumentNullException.ThrowIfNull(_api.EthereumEcdsa);
+
+ _api.PoSSwitcher = AlwaysPoS.Instance;
+
+ _blockCacheService = new BlockCacheService();
+ _api.EthereumEcdsa = new OptimismEthereumEcdsa(_api.EthereumEcdsa);
+ _api.InvalidChainTracker = _invalidChainTracker = new InvalidChainTracker(
+ _api.PoSSwitcher,
+ _api.BlockTree,
+ _blockCacheService,
+ _api.LogManager);
+ _api.DisposeStack.Push(_invalidChainTracker);
+
+ _api.FinalizationManager = _blockFinalizationManager = new ManualBlockFinalizationManager();
+
+ _api.RewardCalculatorSource = NoBlockRewards.Instance;
+ _api.SealValidator = NullSealEngine.Instance;
+ _api.GossipPolicy = ShouldNotGossip.Instance;
+
+ _api.BlockPreprocessor.AddFirst(new MergeProcessingRecoveryStep(_api.PoSSwitcher));
+
+ return Task.CompletedTask;
+ }
+
+ public Task InitSynchronization()
+ {
+ if (_api is null || !ShouldRunSteps(_api))
+ return Task.CompletedTask;
+
+ ArgumentNullException.ThrowIfNull(_api.SpecProvider);
+ ArgumentNullException.ThrowIfNull(_api.BlockTree);
+ ArgumentNullException.ThrowIfNull(_api.DbProvider);
+ ArgumentNullException.ThrowIfNull(_api.PeerDifficultyRefreshPool);
+ ArgumentNullException.ThrowIfNull(_api.SyncPeerPool);
+ ArgumentNullException.ThrowIfNull(_api.NodeStatsManager);
+ ArgumentNullException.ThrowIfNull(_api.BlockchainProcessor);
+
+ ArgumentNullException.ThrowIfNull(_blockCacheService);
+ ArgumentNullException.ThrowIfNull(_invalidChainTracker);
+
+ _invalidChainTracker.SetupBlockchainProcessorInterceptor(_api.BlockchainProcessor);
+
+ _peerRefresher = new PeerRefresher(_api.PeerDifficultyRefreshPool, _api.TimerFactory, _api.LogManager);
+ _api.DisposeStack.Push((PeerRefresher)_peerRefresher);
+
+ _beaconPivot = new BeaconPivot(_syncConfig, _api.DbProvider.MetadataDb, _api.BlockTree, _api.PoSSwitcher, _api.LogManager);
+ _beaconSync = new BeaconSync(_beaconPivot, _api.BlockTree, _syncConfig, _blockCacheService, _api.PoSSwitcher, _api.LogManager);
+ _api.BetterPeerStrategy = new MergeBetterPeerStrategy(null!, _api.PoSSwitcher, _beaconPivot, _api.LogManager);
+ _api.Pivot = _beaconPivot;
+
+ MergeBlockDownloaderFactory blockDownloaderFactory = new MergeBlockDownloaderFactory(
+ _api.PoSSwitcher,
+ _beaconPivot,
+ _api.SpecProvider,
+ _api.BlockValidator!,
+ _api.SealValidator!,
+ _syncConfig,
+ _api.BetterPeerStrategy!,
+ new FullStateFinder(_api.BlockTree, _api.StateReader!),
+ _api.LogManager);
+
+ _api.Synchronizer = new MergeSynchronizer(
+ _api.DbProvider,
+ _api.NodeStorageFactory.WrapKeyValueStore(_api.DbProvider.StateDb),
+ _api.SpecProvider!,
+ _api.BlockTree!,
+ _api.ReceiptStorage!,
+ _api.SyncPeerPool,
+ _api.NodeStatsManager!,
+ _syncConfig,
+ blockDownloaderFactory,
+ _beaconPivot,
+ _api.PoSSwitcher,
+ _mergeConfig,
+ _invalidChainTracker,
+ _api.ProcessExit!,
+ _api.BetterPeerStrategy,
+ _api.ChainSpec,
+ _beaconSync,
+ _api.StateReader!,
+ _api.LogManager
+ );
+
+ _ = new PivotUpdator(
+ _api.BlockTree,
+ _api.Synchronizer.SyncModeSelector,
+ _api.SyncPeerPool,
+ _syncConfig,
+ _blockCacheService,
+ _beaconSync,
+ _api.DbProvider.MetadataDb,
+ _api.LogManager);
+
+ return Task.CompletedTask;
+ }
+
+ public async Task InitRpcModules()
+ {
+ if (_api is null || !ShouldRunSteps(_api))
+ return;
+
+ ArgumentNullException.ThrowIfNull(_api.SpecProvider);
+ ArgumentNullException.ThrowIfNull(_api.BlockProcessingQueue);
+ ArgumentNullException.ThrowIfNull(_api.SyncModeSelector);
+ ArgumentNullException.ThrowIfNull(_api.BlockTree);
+ ArgumentNullException.ThrowIfNull(_api.BlockValidator);
+ ArgumentNullException.ThrowIfNull(_api.RpcModuleProvider);
+ ArgumentNullException.ThrowIfNull(_api.BlockProducer);
+ ArgumentNullException.ThrowIfNull(_api.TxPool);
+
+ ArgumentNullException.ThrowIfNull(_beaconSync);
+ ArgumentNullException.ThrowIfNull(_beaconPivot);
+ ArgumentNullException.ThrowIfNull(_blockCacheService);
+ ArgumentNullException.ThrowIfNull(_invalidChainTracker);
+ ArgumentNullException.ThrowIfNull(_blockFinalizationManager);
+ ArgumentNullException.ThrowIfNull(_peerRefresher);
+
+ // Ugly temporary hack to not receive engine API messages before end of processing of all blocks after restart.
+ // Then we will wait 5s more to ensure everything is processed
+ while (!_api.BlockProcessingQueue.IsEmpty)
+ await Task.Delay(100);
+ await Task.Delay(5000);
+
+ BlockImprovementContextFactory improvementContextFactory = new(
+ _api.BlockProducer,
+ TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot));
+
+ OptimismPayloadPreparationService payloadPreparationService = new(
+ (PostMergeBlockProducer)_api.BlockProducer,
+ improvementContextFactory,
+ _api.TimerFactory,
+ _api.LogManager,
+ TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot));
+
+ _api.RpcCapabilitiesProvider = new EngineRpcCapabilitiesProvider(_api.SpecProvider);
+
+ IInitConfig initConfig = _api.Config<IInitConfig>();
+ IEngineRpcModule engineRpcModule = new EngineRpcModule(
+ new GetPayloadV1Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager),
+ new GetPayloadV2Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager),
+ new GetPayloadV3Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager, _api.CensorshipDetector),
+ new GetPayloadV4Handler(payloadPreparationService, _api.SpecProvider, _api.LogManager, _api.CensorshipDetector),
+ new NewPayloadHandler(
+ _api.BlockValidator,
+ _api.BlockTree,
+ _syncConfig,
+ _api.PoSSwitcher,
+ _beaconSync,
+ _beaconPivot,
+ _blockCacheService,
+ _api.BlockProcessingQueue,
+ _invalidChainTracker,
+ _beaconSync,
+ _api.LogManager,
+ TimeSpan.FromSeconds(_mergeConfig.NewPayloadTimeout),
+ _api.Config<IReceiptConfig>().StoreReceipts),
+ new ForkchoiceUpdatedHandler(
+ _api.BlockTree,
+ _blockFinalizationManager,
+ _api.PoSSwitcher,
+ payloadPreparationService,
+ _api.BlockProcessingQueue,
+ _blockCacheService,
+ _invalidChainTracker,
+ _beaconSync,
+ _beaconPivot,
+ _peerRefresher,
+ _api.SpecProvider,
+ _api.SyncPeerPool!,
+ _api.LogManager,
+ _api.Config<IBlocksConfig>().SecondsPerSlot,
+ _api.Config<IMergeConfig>().SimulateBlockProduction),
+ new GetPayloadBodiesByHashV1Handler(_api.BlockTree, _api.LogManager),
+ new GetPayloadBodiesByRangeV1Handler(_api.BlockTree, _api.LogManager),
+ new GetPayloadBodiesByHashV2Handler(_api.BlockTree, _api.LogManager),
+ new GetPayloadBodiesByRangeV2Handler(_api.BlockTree, _api.LogManager),
+ new ExchangeTransitionConfigurationV1Handler(_api.PoSSwitcher, _api.LogManager),
+ new ExchangeCapabilitiesHandler(_api.RpcCapabilitiesProvider, _api.LogManager),
+ new GetBlobsHandler(_api.TxPool),
+ _api.SpecProvider,
+ new GCKeeper(
+ initConfig.DisableGcOnNewPayload
+ ? NoGCStrategy.Instance
+ : new NoSyncGcRegionStrategy(_api.SyncModeSelector, _mergeConfig), _api.LogManager),
+ _api.LogManager);
+
+ IOptimismEngineRpcModule opEngine = new OptimismEngineRpcModule(engineRpcModule);
+
+ _api.RpcModuleProvider.RegisterSingle(opEngine);
+
+ if (_logger.IsInfo) _logger.Info("Optimism Engine Module has been enabled");
+ }
+
+ public IBlockProducerRunner CreateBlockProducerRunner()
+ {
+ return new StandardBlockProducerRunner(
+ DefaultBlockProductionTrigger,
+ _api!.BlockTree!,
+ _api.BlockProducer!);
+ }
+
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
+
+ public bool MustInitialize => true;
+}
(new)
+91
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3da889a982b2e500be6a52f772b65bc12d4fe743
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using Nethermind.Blockchain;
+using Nethermind.Config;
+using Nethermind.Consensus;
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Producers;
+using Nethermind.Consensus.Transactions;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.Logging;
+using Nethermind.Merge.Plugin.BlockProduction;
+using Nethermind.Optimism.Rpc;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public class OptimismPostMergeBlockProducer : PostMergeBlockProducer
+{
+ private readonly ITxSource _payloadAttrsTxSource;
+
+ public OptimismPostMergeBlockProducer(
+ ITxSource payloadAttrsTxSource,
+ ITxSource txPoolTxSource,
+ IBlockchainProcessor processor,
+ IBlockTree blockTree,
+ IWorldState stateProvider,
+ IGasLimitCalculator gasLimitCalculator,
+ ISealEngine sealEngine,
+ ITimestamper timestamper,
+ ISpecProvider specProvider,
+ ILogManager logManager,
+ IBlocksConfig? miningConfig
+ ) : base(
+ payloadAttrsTxSource.Then(txPoolTxSource),
+ processor,
+ blockTree,
+ stateProvider,
+ gasLimitCalculator,
+ sealEngine,
+ timestamper,
+ specProvider,
+ logManager,
+ miningConfig)
+ {
+ _payloadAttrsTxSource = payloadAttrsTxSource;
+ }
+
+ public override Block PrepareEmptyBlock(BlockHeader parent, PayloadAttributes? payloadAttributes = null)
+ {
+ OptimismPayloadAttributes attrs = (payloadAttributes as OptimismPayloadAttributes)
+ ?? throw new InvalidOperationException("Payload attributes are not set");
+
+ BlockHeader blockHeader = base.PrepareBlockHeader(parent, attrs);
+
+ IEnumerable<Transaction> txs = _payloadAttrsTxSource.GetTransactions(parent, attrs.GasLimit, attrs);
+
+ Block block = new(blockHeader, txs, Array.Empty<BlockHeader>(), payloadAttributes?.Withdrawals);
+
+ if (_producingBlockLock.Wait(BlockProductionTimeoutMs))
+ {
+ try
+ {
+ if (TrySetState(parent.StateRoot))
+ {
+ return ProcessPreparedBlock(block, null) ?? throw new EmptyBlockProductionException("Block processing failed");
+ }
+ else
+ {
+ throw new EmptyBlockProductionException($"Setting state for processing block failed: couldn't set state to stateRoot {parent.StateRoot}");
+ }
+ }
+ finally
+ {
+ _producingBlockLock.Release();
+ }
+ }
+
+ throw new EmptyBlockProductionException("Setting state for processing block failed");
+ }
+
+ protected override void AmendHeader(BlockHeader blockHeader, BlockHeader parent)
+ {
+ base.AmendHeader(blockHeader, parent);
+
+ blockHeader.ExtraData = Array.Empty<byte>();
+ }
+}
(new)
+38
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b374b105bed1135f9c4f265335fb3ac28eaf34d4
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReadOnlyTxProcessingEnv.cs
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Consensus.Processing;
+using Nethermind.Core.Specs;
+using Nethermind.Evm;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+using Nethermind.State;
+using System;
+
+namespace Nethermind.Optimism;
+
+public class OptimismReadOnlyTxProcessingEnv(
+ IWorldStateManager worldStateManager,
+ IReadOnlyBlockTree readOnlyBlockTree,
+ ISpecProvider specProvider,
+ ILogManager logManager,
+ IL1CostHelper l1CostHelper,
+ IOptimismSpecHelper opSpecHelper,
+ IWorldState? worldStateToWarmUp = null) : ReadOnlyTxProcessingEnv(
+ worldStateManager,
+ readOnlyBlockTree,
+ specProvider,
+ logManager,
+ worldStateToWarmUp
+ )
+{
+ protected override ITransactionProcessor CreateTransactionProcessor()
+ {
+ ArgumentNullException.ThrowIfNull(LogManager);
+
+ BlockhashProvider blockhashProvider = new(BlockTree, SpecProvider, StateProvider, LogManager);
+ VirtualMachine virtualMachine = new(blockhashProvider, SpecProvider, CodeInfoRepository, LogManager);
+ return new OptimismTransactionProcessor(SpecProvider, StateProvider, virtualMachine, LogManager, l1CostHelper, opSpecHelper, CodeInfoRepository);
+ }
+}
(new)
+202
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..186fc2375d358dc968d3695b51638d499a88da48
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs
@@ -0,0 +1,202 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Serialization.Rlp;
+
+namespace Nethermind.Optimism;
+
+[Rlp.Decoder(RlpDecoderKey.Trie)]
+public class OptimismReceiptTrieDecoder() : OptimismReceiptMessageDecoder(true) { }
+
+[Rlp.Decoder]
+public class OptimismReceiptMessageDecoder(bool isEncodedForTrie = false) : IRlpStreamDecoder<OptimismTxReceipt>, IRlpStreamDecoder<TxReceipt>
+{
+ private readonly bool _isEncodedForTrie = isEncodedForTrie;
+
+ public OptimismTxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ OptimismTxReceipt txReceipt = new();
+ if (!rlpStream.IsSequenceNext())
+ {
+ rlpStream.SkipLength();
+ txReceipt.TxType = (TxType)rlpStream.ReadByte();
+ }
+
+ int lastCheck = rlpStream.ReadSequenceLength() + rlpStream.Position;
+
+ byte[] firstItem = rlpStream.DecodeByteArray();
+ if (firstItem.Length == 1 && (firstItem[0] == 0 || firstItem[0] == 1))
+ {
+ txReceipt.StatusCode = firstItem[0];
+ txReceipt.GasUsedTotal = (long)rlpStream.DecodeUBigInt();
+ }
+ else if (firstItem.Length is >= 1 and <= 4)
+ {
+ txReceipt.GasUsedTotal = (long)firstItem.ToUnsignedBigInteger();
+ txReceipt.SkipStateAndStatusInRlp = true;
+ }
+ else
+ {
+ txReceipt.PostTransactionState = firstItem.Length == 0 ? null : new Hash256(firstItem);
+ txReceipt.GasUsedTotal = (long)rlpStream.DecodeUBigInt();
+ }
+
+ txReceipt.Bloom = rlpStream.DecodeBloom();
+
+ int logEntriesCheck = rlpStream.ReadSequenceLength() + rlpStream.Position;
+
+ int numberOfReceipts = rlpStream.PeekNumberOfItemsRemaining(logEntriesCheck);
+ LogEntry[] entries = new LogEntry[numberOfReceipts];
+ for (int i = 0; i < numberOfReceipts; i++)
+ {
+ entries[i] = Rlp.Decode<LogEntry>(rlpStream, RlpBehaviors.AllowExtraBytes);
+ }
+ txReceipt.Logs = entries;
+
+ if (lastCheck > rlpStream.Position)
+ {
+ if (txReceipt.TxType == TxType.DepositTx && lastCheck > rlpStream.Position)
+ {
+ txReceipt.DepositNonce = rlpStream.DecodeUlong();
+
+ if (lastCheck > rlpStream.Position)
+ {
+ txReceipt.DepositReceiptVersion = rlpStream.DecodeUlong();
+ }
+ }
+ }
+
+ return txReceipt;
+ }
+
+ private (int Total, int Logs) GetContentLength(OptimismTxReceipt item, RlpBehaviors rlpBehaviors)
+ {
+ if (item is null)
+ {
+ return (0, 0);
+ }
+
+ int contentLength = 0;
+ contentLength += Rlp.LengthOf(item.GasUsedTotal);
+ contentLength += Rlp.LengthOf(item.Bloom);
+
+ int logsLength = GetLogsLength(item);
+ contentLength += Rlp.LengthOfSequence(logsLength);
+
+ bool isEip658Receipts = (rlpBehaviors & RlpBehaviors.Eip658Receipts) == RlpBehaviors.Eip658Receipts;
+
+ if (!item.SkipStateAndStatusInRlp)
+ {
+ contentLength += isEip658Receipts
+ ? Rlp.LengthOf(item.StatusCode)
+ : Rlp.LengthOf(item.PostTransactionState);
+ }
+
+ if (item.TxType == TxType.DepositTx && item.DepositNonce is not null &&
+ (item.DepositReceiptVersion is not null || !_isEncodedForTrie))
+ {
+ contentLength += Rlp.LengthOf(item.DepositNonce);
+
+ if (item.DepositReceiptVersion is not null)
+ {
+ contentLength += Rlp.LengthOf(item.DepositReceiptVersion.Value);
+ }
+ }
+
+ return (contentLength, logsLength);
+ }
+
+ private static int GetLogsLength(OptimismTxReceipt item)
+ {
+ int logsLength = 0;
+ for (var i = 0; i < item.Logs?.Length; i++)
+ {
+ logsLength += Rlp.LengthOf(item.Logs[i]);
+ }
+
+ return logsLength;
+ }
+
+ /// <summary>
+ /// https://eips.ethereum.org/EIPS/eip-2718
+ /// </summary>
+ public int GetLength(OptimismTxReceipt item, RlpBehaviors rlpBehaviors)
+ {
+ (int Total, int Logs) length = GetContentLength(item, rlpBehaviors);
+ int receiptPayloadLength = Rlp.LengthOfSequence(length.Total);
+
+ bool isForTxRoot = (rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.SkipTypedWrapping;
+ int result = item.TxType != TxType.Legacy
+ ? isForTxRoot
+ ? (1 + receiptPayloadLength)
+ : Rlp.LengthOfSequence(1 + receiptPayloadLength) // Rlp(TransactionType || TransactionPayload)
+ : receiptPayloadLength;
+ return result;
+ }
+
+ public void Encode(RlpStream rlpStream, OptimismTxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ if (item is null)
+ {
+ rlpStream.EncodeNullObject();
+ return;
+ }
+
+ (int totalContentLength, int logsLength) = GetContentLength(item, rlpBehaviors);
+ int sequenceLength = Rlp.LengthOfSequence(totalContentLength);
+
+ if (item.TxType != TxType.Legacy)
+ {
+ if ((rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.None)
+ {
+ rlpStream.StartByteArray(sequenceLength + 1, false);
+ }
+
+ rlpStream.WriteByte((byte)item.TxType);
+ }
+
+ rlpStream.StartSequence(totalContentLength);
+ if (!item.SkipStateAndStatusInRlp)
+ {
+ rlpStream.Encode(item.StatusCode);
+ }
+
+ rlpStream.Encode(item.GasUsedTotal);
+ rlpStream.Encode(item.Bloom);
+
+ rlpStream.StartSequence(logsLength);
+ for (var i = 0; i < item.Logs?.Length; i++)
+ {
+ rlpStream.Encode(item.Logs[i]);
+ }
+
+ if (item.TxType == TxType.DepositTx && item.DepositNonce is not null &&
+ (item.DepositReceiptVersion is not null || !_isEncodedForTrie))
+ {
+ rlpStream.Encode(item.DepositNonce.Value);
+
+ if (item.DepositReceiptVersion is not null)
+ {
+ rlpStream.Encode(item.DepositReceiptVersion.Value);
+ }
+ }
+ }
+
+ TxReceipt IRlpStreamDecoder<TxReceipt>.Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ return Decode(rlpStream, rlpBehaviors);
+ }
+
+ public void Encode(RlpStream stream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ Encode(stream, (OptimismTxReceipt)item, rlpBehaviors);
+ }
+
+ public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors)
+ {
+ return GetLength((OptimismTxReceipt)item, rlpBehaviors);
+ }
+}
(new)
+336
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d3e95e82ef13d0bef6eaed7fc2f6ce0bbbff39f2
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs
@@ -0,0 +1,336 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.Collections;
+using Nethermind.Core.Crypto;
+using Nethermind.Core;
+using Nethermind.Serialization.Rlp;
+using static Nethermind.Serialization.Rlp.Rlp;
+
+namespace Nethermind.Optimism;
+
+[Decoder(RlpDecoderKey.Storage)]
+public class OptimismCompactReceiptStorageDecoder :
+ IRlpStreamDecoder<OptimismTxReceipt>, IRlpValueDecoder<OptimismTxReceipt>, IRlpObjectDecoder<OptimismTxReceipt>, IReceiptRefDecoder,
+ IRlpStreamDecoder<TxReceipt>, IRlpValueDecoder<TxReceipt>, IRlpObjectDecoder<TxReceipt>
+{
+ public OptimismTxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ if (rlpStream.IsNextItemNull())
+ {
+ rlpStream.ReadByte();
+ return null!;
+ }
+
+ OptimismTxReceipt txReceipt = new();
+ int lastCheck = rlpStream.ReadSequenceLength() + rlpStream.Position;
+
+ byte[] firstItem = rlpStream.DecodeByteArray();
+ if (firstItem.Length == 1)
+ {
+ txReceipt.StatusCode = firstItem[0];
+ }
+ else
+ {
+ txReceipt.PostTransactionState = firstItem.Length == 0 ? null : new Hash256(firstItem);
+ }
+
+ txReceipt.Sender = rlpStream.DecodeAddress();
+ txReceipt.GasUsedTotal = (long)rlpStream.DecodeUBigInt();
+
+ int sequenceLength = rlpStream.ReadSequenceLength();
+ int logEntriesCheck = sequenceLength + rlpStream.Position;
+ using ArrayPoolList<LogEntry> logEntries = new(sequenceLength * 2 / LengthOfAddressRlp);
+
+ while (rlpStream.Position < logEntriesCheck)
+ {
+ logEntries.Add(CompactLogEntryDecoder.Decode(rlpStream, RlpBehaviors.AllowExtraBytes)!);
+ }
+
+ txReceipt.Logs = [.. logEntries];
+
+ if (lastCheck > rlpStream.Position)
+ {
+ int remainingItems = rlpStream.PeekNumberOfItemsRemaining(lastCheck);
+ if (remainingItems > 0)
+ {
+ txReceipt.DepositNonce = rlpStream.DecodeUlong();
+ }
+
+ if (remainingItems > 1)
+ {
+ txReceipt.DepositReceiptVersion = rlpStream.DecodeUlong();
+ }
+ }
+
+ bool allowExtraBytes = (rlpBehaviors & RlpBehaviors.AllowExtraBytes) != 0;
+ if (!allowExtraBytes)
+ {
+ rlpStream.Check(lastCheck);
+ }
+
+ txReceipt.Bloom = new Bloom(txReceipt.Logs);
+
+ return txReceipt;
+ }
+
+ public OptimismTxReceipt Decode(ref ValueDecoderContext decoderContext,
+ RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ if (decoderContext.IsNextItemNull())
+ {
+ decoderContext.ReadByte();
+ return null!;
+ }
+
+ OptimismTxReceipt txReceipt = new();
+ int lastCheck = decoderContext.ReadSequenceLength() + decoderContext.Position;
+
+ byte[] firstItem = decoderContext.DecodeByteArray();
+ if (firstItem.Length == 1)
+ {
+ txReceipt.StatusCode = firstItem[0];
+ }
+ else
+ {
+ txReceipt.PostTransactionState = firstItem.Length == 0 ? null : new Hash256(firstItem);
+ }
+
+ txReceipt.Sender = decoderContext.DecodeAddress();
+ txReceipt.GasUsedTotal = (long)decoderContext.DecodeUBigInt();
+
+ int sequenceLength = decoderContext.ReadSequenceLength();
+ int logEntriesCheck = sequenceLength + decoderContext.Position;
+
+ // Don't know the size exactly, I'll just assume its just an address and add some margin
+ using ArrayPoolList<LogEntry> logEntries = new(sequenceLength * 2 / LengthOfAddressRlp);
+ while (decoderContext.Position < logEntriesCheck)
+ {
+ logEntries.Add(CompactLogEntryDecoder.Decode(ref decoderContext, RlpBehaviors.AllowExtraBytes)!);
+ }
+
+ txReceipt.Logs = [.. logEntries];
+
+ if (lastCheck > decoderContext.Position)
+ {
+ int remainingItems = decoderContext.PeekNumberOfItemsRemaining(lastCheck);
+ if (remainingItems > 0)
+ {
+ txReceipt.DepositNonce = decoderContext.DecodeULong();
+ }
+
+ if (remainingItems > 1)
+ {
+ txReceipt.DepositReceiptVersion = decoderContext.DecodeULong();
+ }
+ }
+
+ bool allowExtraBytes = (rlpBehaviors & RlpBehaviors.AllowExtraBytes) != 0;
+ if (!allowExtraBytes)
+ {
+ decoderContext.Check(lastCheck);
+ }
+
+ txReceipt.Bloom = new Bloom(txReceipt.Logs);
+
+ return txReceipt;
+ }
+
+ public void DecodeStructRef(scoped ref ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors,
+ out TxReceiptStructRef item)
+ {
+ // Note: This method runs at 2.5 million times/sec on my machine
+ item = new TxReceiptStructRef();
+
+ if (decoderContext.IsNextItemNull())
+ {
+ decoderContext.ReadByte();
+ return;
+ }
+
+ int lastCheck = decoderContext.ReadSequenceLength() + decoderContext.Position;
+
+ ReadOnlySpan<byte> firstItem = decoderContext.DecodeByteArraySpan();
+ if (firstItem.Length == 1)
+ {
+ item.StatusCode = firstItem[0];
+ }
+ else
+ {
+ item.PostTransactionState =
+ firstItem.Length == 0 ? new Hash256StructRef() : new Hash256StructRef(firstItem);
+ }
+
+ decoderContext.DecodeAddressStructRef(out item.Sender);
+ item.GasUsedTotal = (long)decoderContext.DecodeUBigInt();
+
+ (int PrefixLength, int ContentLength) peekPrefixAndContentLength =
+ decoderContext.PeekPrefixAndContentLength();
+ int logsBytes = peekPrefixAndContentLength.ContentLength + peekPrefixAndContentLength.PrefixLength;
+ item.LogsRlp = decoderContext.Data.Slice(decoderContext.Position, logsBytes);
+
+ if (lastCheck > decoderContext.Position)
+ {
+ int remainingItems = decoderContext.PeekNumberOfItemsRemaining(lastCheck);
+
+ if (remainingItems > 1)
+ {
+ decoderContext.SkipItem();
+ }
+
+ if (remainingItems > 2)
+ {
+ decoderContext.SkipItem();
+ }
+ }
+
+ decoderContext.SkipItem();
+ }
+
+ public void DecodeLogEntryStructRef(scoped ref ValueDecoderContext decoderContext, RlpBehaviors none,
+ out LogEntryStructRef current)
+ {
+ CompactLogEntryDecoder.DecodeLogEntryStructRef(ref decoderContext, none, out current);
+ }
+
+ public Hash256[] DecodeTopics(ValueDecoderContext valueDecoderContext)
+ {
+ return CompactLogEntryDecoder.DecodeTopics(valueDecoderContext);
+ }
+
+ // Refstruct decode does not generate bloom
+ public bool CanDecodeBloom => false;
+
+ public Rlp Encode(OptimismTxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ RlpStream rlpStream = new(GetLength(item!, rlpBehaviors));
+ Encode(rlpStream, item, rlpBehaviors);
+ return new Rlp(rlpStream.Data.ToArray()!);
+ }
+
+ public void Encode(RlpStream rlpStream, OptimismTxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ if (item is null)
+ {
+ rlpStream.EncodeNullObject();
+ return;
+ }
+
+ (int totalContentLength, int logsLength) = GetContentLength(item, rlpBehaviors);
+
+ bool isEip658receipts = (rlpBehaviors & RlpBehaviors.Eip658Receipts) == RlpBehaviors.Eip658Receipts;
+
+ // Note: Any byte saved here is about 3GB on mainnet.
+ rlpStream.StartSequence(totalContentLength);
+ if (isEip658receipts)
+ {
+ rlpStream.Encode(item.StatusCode);
+ }
+ else
+ {
+ rlpStream.Encode(item.PostTransactionState);
+ }
+
+ rlpStream.Encode(item.Sender);
+ rlpStream.Encode(item.GasUsedTotal);
+
+ rlpStream.StartSequence(logsLength);
+
+ LogEntry[] logs = item.Logs ?? Array.Empty<LogEntry>();
+ for (int i = 0; i < logs.Length; i++)
+ {
+ CompactLogEntryDecoder.Encode(rlpStream, logs[i]);
+ }
+
+ if (item.TxType == TxType.DepositTx && item.DepositNonce is not null)
+ {
+ rlpStream.Encode(item.DepositNonce.Value);
+
+ if (item.DepositReceiptVersion is not null)
+ {
+ rlpStream.Encode(item.DepositReceiptVersion.Value);
+ }
+ }
+ }
+
+ private static (int Total, int Logs) GetContentLength(OptimismTxReceipt? item, RlpBehaviors rlpBehaviors)
+ {
+ int contentLength = 0;
+ if (item is null)
+ {
+ return (contentLength, 0);
+ }
+
+ bool isEip658Receipts = (rlpBehaviors & RlpBehaviors.Eip658Receipts) == RlpBehaviors.Eip658Receipts;
+ if (isEip658Receipts)
+ {
+ contentLength += LengthOf(item.StatusCode);
+ }
+ else
+ {
+ contentLength += LengthOf(item.PostTransactionState);
+ }
+
+ contentLength += LengthOf(item.Sender);
+ contentLength += LengthOf(item.GasUsedTotal);
+
+ int logsLength = GetLogsLength(item);
+ contentLength += LengthOfSequence(logsLength);
+
+ if (item.TxType == TxType.DepositTx && item.DepositNonce is not null)
+ {
+ contentLength += LengthOf(item.DepositNonce);
+
+ if (item.DepositReceiptVersion is not null)
+ {
+ contentLength += LengthOf(item.DepositReceiptVersion.Value);
+ }
+ }
+
+ return (contentLength, logsLength);
+ }
+
+ private static int GetLogsLength(OptimismTxReceipt item)
+ {
+ int logsLength = 0;
+ LogEntry[] logs = item.Logs ?? Array.Empty<LogEntry>();
+ for (int i = 0; i < logs.Length; i++)
+ {
+ logsLength += CompactLogEntryDecoder.Instance.GetLength(logs[i]);
+ }
+
+ return logsLength;
+ }
+
+ public int GetLength(OptimismTxReceipt item, RlpBehaviors rlpBehaviors)
+ {
+ (int Total, int Logs) length = GetContentLength(item, rlpBehaviors);
+ return LengthOfSequence(length.Total);
+ }
+
+ TxReceipt IRlpStreamDecoder<TxReceipt>.Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
+ {
+ return Decode(rlpStream, rlpBehaviors);
+ }
+
+ public void Encode(RlpStream stream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ Encode(stream, (OptimismTxReceipt)item, rlpBehaviors);
+ }
+
+ public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors)
+ {
+ return GetLength((OptimismTxReceipt)item, rlpBehaviors);
+ }
+
+ TxReceipt IRlpValueDecoder<TxReceipt>.Decode(ref ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors)
+ {
+ return Decode(ref decoderContext, rlpBehaviors);
+ }
+
+ public Rlp Encode(TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ return Encode((OptimismTxReceipt?)item, rlpBehaviors);
+ }
+}
(new)
+168
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d21a3350ed39e962e9c56ffb1a162b24566a18e7
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs
@@ -0,0 +1,168 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.Evm;
+using Nethermind.Evm.Tracing;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Int256;
+using Nethermind.Logging;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+public sealed class OptimismTransactionProcessor(
+ ISpecProvider specProvider,
+ IWorldState worldState,
+ IVirtualMachine virtualMachine,
+ ILogManager logManager,
+ IL1CostHelper l1CostHelper,
+ IOptimismSpecHelper opSpecHelper,
+ ICodeInfoRepository? codeInfoRepository
+ ) : TransactionProcessorBase(specProvider, worldState, virtualMachine, codeInfoRepository, logManager)
+{
+ private UInt256? _currentTxL1Cost;
+
+ protected override TransactionResult Execute(Transaction tx, in BlockExecutionContext blCtx, ITxTracer tracer, ExecutionOptions opts)
+ {
+ if (tx.SupportsBlobs)
+ {
+ // No blob txs in optimism
+ return TransactionResult.MalformedTransaction;
+ }
+
+ IReleaseSpec spec = SpecProvider.GetSpec(blCtx.Header);
+ _currentTxL1Cost = null;
+ if (tx.IsDeposit())
+ {
+ WorldState.AddToBalanceAndCreateIfNotExists(tx.SenderAddress!, tx.Mint, spec);
+ }
+
+ Snapshot snapshot = WorldState.TakeSnapshot();
+
+ TransactionResult result = base.Execute(tx, blCtx, tracer, opts);
+
+ if (!result && tx.IsDeposit() && result.Error != "block gas limit exceeded")
+ {
+ // deposit tx should be included
+ WorldState.Restore(snapshot);
+ if (!WorldState.AccountExists(tx.SenderAddress!))
+ {
+ WorldState.CreateAccount(tx.SenderAddress!, 0, 1);
+ }
+ else
+ {
+ WorldState.IncrementNonce(tx.SenderAddress!);
+ }
+ blCtx.Header.GasUsed += tx.GasLimit;
+ tracer.MarkAsFailed(tx.To!, tx.GasLimit, Array.Empty<byte>(), $"failed deposit: {result.Error}");
+ result = TransactionResult.Ok;
+ }
+
+ return result;
+ }
+
+ protected override TransactionResult BuyGas(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts,
+ in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment)
+ {
+ premiumPerGas = UInt256.Zero;
+ senderReservedGasPayment = UInt256.Zero;
+
+ bool validate = !opts.HasFlag(ExecutionOptions.NoValidation);
+
+ UInt256 senderBalance = WorldState.GetBalance(tx.SenderAddress!);
+
+ if (tx.IsDeposit() && !tx.IsOPSystemTransaction && senderBalance < tx.Value)
+ {
+ return "insufficient sender balance";
+ }
+
+ if (validate && !tx.IsDeposit())
+ {
+ if (!tx.TryCalculatePremiumPerGas(header.BaseFeePerGas, out premiumPerGas))
+ {
+ TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE");
+ return "miner premium is negative";
+ }
+
+ if (UInt256.SubtractUnderflow(senderBalance, tx.Value, out UInt256 balanceLeft))
+ {
+ TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}");
+ return "insufficient sender balance";
+ }
+
+ UInt256 l1Cost = _currentTxL1Cost ??= l1CostHelper.ComputeL1Cost(tx, header, WorldState);
+ if (UInt256.SubtractUnderflow(balanceLeft, l1Cost, out balanceLeft))
+ {
+ TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}");
+ return "insufficient sender balance";
+ }
+
+ bool overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee);
+ if (spec.IsEip1559Enabled && !tx.IsFree() && (overflows || balanceLeft < maxGasFee))
+ {
+ TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}");
+ return "insufficient MaxFeePerGas for sender balance";
+ }
+
+ overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment);
+ if (overflows || senderReservedGasPayment > balanceLeft)
+ {
+ TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}");
+ return "insufficient sender balance";
+ }
+
+ senderReservedGasPayment += l1Cost; // no overflow here, otherwise previous check would fail
+ }
+
+ if (validate)
+ WorldState.SubtractFromBalance(tx.SenderAddress!, senderReservedGasPayment, spec);
+
+ return TransactionResult.Ok;
+ }
+
+ protected override TransactionResult IncrementNonce(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts)
+ {
+ if (!tx.IsDeposit())
+ return base.IncrementNonce(tx, header, spec, tracer, opts);
+
+ WorldState.IncrementNonce(tx.SenderAddress!);
+ return TransactionResult.Ok;
+ }
+
+ protected override TransactionResult ValidateSender(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) =>
+ tx.IsDeposit() ? TransactionResult.Ok : base.ValidateSender(tx, header, spec, tracer, opts);
+
+ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer,
+ in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in byte statusCode)
+ {
+ if (!tx.IsDeposit())
+ {
+ // Skip coinbase payments for deposit tx in Regolith
+ base.PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, statusCode);
+
+ if (opSpecHelper.IsBedrock(header))
+ {
+ UInt256 l1Cost = _currentTxL1Cost ??= l1CostHelper.ComputeL1Cost(tx, header, WorldState);
+ WorldState.AddToBalanceAndCreateIfNotExists(opSpecHelper.L1FeeReceiver, l1Cost, spec);
+ }
+ }
+ }
+
+ protected override long Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts,
+ in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice)
+ {
+ // if deposit: skip refunds, skip tipping coinbase
+ // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used.
+ if (tx.IsDeposit() && !opSpecHelper.IsRegolith(header))
+ {
+ // Record deposits as using all their gas
+ // System Transactions are special & are not recorded as using any gas (anywhere)
+ return tx.IsOPSystemTransaction ? 0 : tx.GasLimit;
+ }
+
+ return base.Refund(tx, header, spec, opts, substate, unspentGas, gasPrice);
+ }
+}
(new)
+29
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionsExecutorFactory.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionsExecutorFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..951d01542a81dbb5428eb70cbd3b3c3c401e1a8c
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTransactionsExecutorFactory.cs
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Producers;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+
+namespace Nethermind.Optimism;
+
+public class OptimismTransactionsExecutorFactory : IBlockTransactionsExecutorFactory
+{
+ private readonly ISpecProvider _specProvider;
+ private readonly ILogManager _logManager;
+
+ public OptimismTransactionsExecutorFactory(ISpecProvider specProvider, ILogManager logManager)
+ {
+ _specProvider = specProvider;
+ _logManager = logManager;
+ }
+
+ public IBlockProcessor.IBlockTransactionsExecutor Create(IReadOnlyTxProcessingScope readOnlyTxProcessingEnv)
+ {
+ return new BlockProcessor.BlockProductionTransactionsExecutor(readOnlyTxProcessingEnv.TransactionProcessor,
+ readOnlyTxProcessingEnv.WorldState, new OptimismBlockProductionTransactionPicker(_specProvider),
+ _logManager);
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9bb2e77fcddefe1f635abd3eaa25fbec96db99ad
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Serialization.Rlp;
+using Nethermind.Serialization.Rlp.TxDecoders;
+
+namespace Nethermind.Optimism;
+
+public sealed class OptimismTxDecoder<T>(Func<T>? transactionFactory = null)
+ : BaseEIP1559TxDecoder<T>(TxType.DepositTx, transactionFactory) where T : Transaction, new()
+{
+ protected override int GetSignatureLength(Signature? signature, bool forSigning, bool isEip155Enabled = false, ulong chainId = 0) => 0;
+
+ protected override void EncodeSignature(Signature? signature, RlpStream stream, bool forSigning, bool isEip155Enabled = false, ulong chainId = 0)
+ {
+ }
+
+ protected override void DecodePayload(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ transaction.SourceHash = rlpStream.DecodeKeccak();
+ transaction.SenderAddress = rlpStream.DecodeAddress();
+ transaction.To = rlpStream.DecodeAddress();
+ transaction.Mint = rlpStream.DecodeUInt256();
+ transaction.Value = rlpStream.DecodeUInt256();
+ transaction.GasLimit = rlpStream.DecodeLong();
+ transaction.IsOPSystemTransaction = rlpStream.DecodeBool();
+ transaction.Data = rlpStream.DecodeByteArray();
+ }
+
+ protected override void DecodePayload(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext,
+ RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ transaction.SourceHash = decoderContext.DecodeKeccak();
+ transaction.SenderAddress = decoderContext.DecodeAddress();
+ transaction.To = decoderContext.DecodeAddress();
+ transaction.Mint = decoderContext.DecodeUInt256();
+ transaction.Value = decoderContext.DecodeUInt256();
+ transaction.GasLimit = decoderContext.DecodeLong();
+ transaction.IsOPSystemTransaction = decoderContext.DecodeBool();
+ transaction.Data = decoderContext.DecodeByteArray();
+ }
+
+ protected override Signature? DecodeSignature(ulong v, ReadOnlySpan<byte> rBytes, ReadOnlySpan<byte> sBytes, Signature? fallbackSignature = null, RlpBehaviors rlpBehaviors = RlpBehaviors.None) =>
+ v == 0 && rBytes.IsEmpty && sBytes.IsEmpty
+ ? fallbackSignature
+ : base.DecodeSignature(v, rBytes, sBytes, fallbackSignature, rlpBehaviors);
+
+ protected override int GetPayloadLength(Transaction transaction) =>
+ Rlp.LengthOf(transaction.SourceHash)
+ + Rlp.LengthOf(transaction.SenderAddress)
+ + Rlp.LengthOf(transaction.To)
+ + Rlp.LengthOf(transaction.Mint)
+ + Rlp.LengthOf(transaction.Value)
+ + Rlp.LengthOf(transaction.GasLimit)
+ + Rlp.LengthOf(transaction.IsOPSystemTransaction)
+ + Rlp.LengthOf(transaction.Data);
+
+ protected override void EncodePayload(Transaction transaction, RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
+ {
+ stream.Encode(transaction.SourceHash);
+ stream.Encode(transaction.SenderAddress);
+ stream.Encode(transaction.To);
+ stream.Encode(transaction.Mint);
+ stream.Encode(transaction.Value);
+ stream.Encode(transaction.GasLimit);
+ stream.Encode(transaction.IsOPSystemTransaction);
+ stream.Encode(transaction.Data);
+ }
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxPoolTxSource.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxPoolTxSource.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7334e9448b45d4feae8ed17f4800900892e9af40
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxPoolTxSource.cs
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections.Generic;
+using System.Linq;
+using Nethermind.Consensus.Producers;
+using Nethermind.Consensus.Transactions;
+using Nethermind.Core;
+using Nethermind.Optimism.Rpc;
+
+namespace Nethermind.Optimism;
+
+public class OptimismTxPoolTxSource : ITxSource
+{
+ private readonly ITxSource _baseTxSource;
+
+ public OptimismTxPoolTxSource(ITxSource baseTxSource)
+ {
+ _baseTxSource = baseTxSource;
+ }
+
+ public IEnumerable<Transaction> GetTransactions(BlockHeader parent, long gasLimit, PayloadAttributes? payloadAttributes) =>
+ payloadAttributes is OptimismPayloadAttributes { NoTxPool: true }
+ ? Enumerable.Empty<Transaction>()
+ : _baseTxSource.GetTransactions(parent, gasLimit, payloadAttributes);
+}
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxReceipt.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxReceipt.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3d1dcf83883b5c605683a22bf13e99a8bc2e9ff8
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/OptimismTxReceipt.cs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+
+namespace Nethermind.Optimism;
+
+public class OptimismTxReceipt : TxReceipt
+{
+ public OptimismTxReceipt()
+ {
+
+ }
+
+ public OptimismTxReceipt(TxReceipt receipt) : base(receipt)
+ {
+
+ }
+ public ulong? DepositNonce { get; set; }
+ public ulong? DepositReceiptVersion { get; set; }
+}
(new)
+74
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/ReadOnlyChainProcessingEnv.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/ReadOnlyChainProcessingEnv.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3e1e3cbe011e5be3f28430ee7a48d081552e559c
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/ReadOnlyChainProcessingEnv.cs
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Blockchain.BeaconBlockRoot;
+using Nethermind.Blockchain.Blocks;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Consensus.Validators;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+using Nethermind.State;
+
+namespace Nethermind.Optimism;
+
+/// <summary>
+/// Not thread safe.
+/// </summary>
+public class OptimismReadOnlyChainProcessingEnv(
+ IReadOnlyTxProcessingScope txEnv,
+ IBlockValidator blockValidator,
+ IBlockPreprocessorStep recoveryStep,
+ IRewardCalculator rewardCalculator,
+ IReceiptStorage receiptStorage,
+ ISpecProvider specProvider,
+ IBlockTree blockTree,
+ IStateReader stateReader,
+ ILogManager logManager,
+ IOptimismSpecHelper opSpecHelper,
+ Create2DeployerContractRewriter contractRewriter,
+ IWithdrawalProcessor? withdrawalProcessor,
+ IBlockProcessor.IBlockTransactionsExecutor? blockTransactionsExecutor = null) : ReadOnlyChainProcessingEnv(
+ txEnv,
+ blockValidator,
+ recoveryStep,
+ rewardCalculator,
+ receiptStorage,
+ specProvider,
+ blockTree,
+ stateReader,
+ logManager,
+ blockTransactionsExecutor)
+{
+
+ protected override IBlockProcessor CreateBlockProcessor(
+ IReadOnlyTxProcessingScope scope,
+ IBlockTree blockTree,
+ IBlockValidator blockValidator,
+ IRewardCalculator rewardCalculator,
+ IReceiptStorage receiptStorage,
+ ISpecProvider specProvider,
+ ILogManager logManager,
+ IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor
+ )
+ {
+ return new OptimismBlockProcessor(
+ specProvider,
+ blockValidator,
+ rewardCalculator,
+ transactionsExecutor,
+ scope.WorldState,
+ receiptStorage,
+ scope.TransactionProcessor,
+ new BlockhashStore(specProvider, scope.WorldState),
+ new BeaconBlockRootHandler(scope.TransactionProcessor),
+ logManager,
+ opSpecHelper,
+ contractRewriter,
+ withdrawalProcessor);
+ }
+}
(new)
+72
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c8eae2f7a2c3d8766824c04ab378ca318240a2e8
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Core.Crypto;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.Merge.Plugin.Data;
+
+namespace Nethermind.Optimism.Rpc;
+
+
+[RpcModule(ModuleType.Engine)]
+public interface IOptimismEngineRpcModule : IRpcModule
+{
+ [JsonRpcMethod(
+ Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null);
+
+ [JsonRpcMethod(
+ Description = "Returns the most recent version of an execution payload with respect to the transaction set contained by the mempool.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<ExecutionPayload?>> engine_getPayloadV1(byte[] payloadId);
+
+ [JsonRpcMethod(
+ Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayload executionPayload);
+
+ [JsonRpcMethod(
+ Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV2(ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null);
+
+ [JsonRpcMethod(
+ Description = "Returns the most recent version of an execution payload with respect to the transaction set contained by the mempool.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<GetPayloadV2Result?>> engine_getPayloadV2(byte[] payloadId);
+
+ [JsonRpcMethod(
+ Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV2(ExecutionPayload executionPayload);
+
+ [JsonRpcMethod(
+ Description =
+ "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV3(
+ ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null);
+
+ [JsonRpcMethod(
+ Description = "Returns the most recent version of an execution payload with respect to the transaction set contained by the mempool.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<OptimismGetPayloadV3Result?>> engine_getPayloadV3(byte[] payloadId);
+
+ [JsonRpcMethod(
+ Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.",
+ IsSharable = true,
+ IsImplemented = true)]
+ Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV3(ExecutionPayloadV3 executionPayload,
+ byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot);
+}
(new)
+50
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEthRpcModule.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEthRpcModule.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6fd24f38c871c0f685f040540f8c0cb79c9fff6d
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEthRpcModule.cs
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.JsonRpc.Modules.Eth;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.Core.Crypto;
+using Nethermind.JsonRpc;
+using Nethermind.Blockchain.Find;
+using Nethermind.Int256;
+using System.Threading.Tasks;
+using Nethermind.Facade.Eth;
+
+namespace Nethermind.Optimism.Rpc;
+
+[RpcModule(ModuleType.Eth)]
+public interface IOptimismEthRpcModule : IEthRpcModule
+{
+ [JsonRpcMethod(Description = "Get receipts from all transactions from particular block, more efficient than fetching the receipts one-by-one.",
+ IsImplemented = true,
+ ExampleResponse = "{\"jsonrpc\":\"2.0\",\"result\":[{\"transactionHash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"transactionIndex\":\"0x0\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"cumulativeGasUsed\":\"0x5208\",\"gasUsed\":\"0x5208\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":null,\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"status\":\"0x1\",\"type\":\"0x0\"},{\"transactionHash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"transactionIndex\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"cumulativeGasUsed\":\"0xa410\",\"gasUsed\":\"0x5208\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":null,\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"status\":\"0x1\",\"type\":\"0x0\"}],\"id\":67}")]
+ new ResultWrapper<OptimismReceiptForRpc[]?> eth_getBlockReceipts([JsonRpcParameter(ExampleValue = "latest")] BlockParameter blockParameter);
+
+
+ [JsonRpcMethod(IsImplemented = true,
+ Description = "Retrieves a transaction receipt by tx hash",
+ IsSharable = true,
+ ExampleResponse = "{\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"transactionIndex\":\"0x7\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"cumulativeGasUsed\":\"0x62c9d\",\"gasUsed\":\"0xe384\",\"effectiveGasPrice\":\"0x12a05f200\",\"from\":\"0x0afe0a94415e8974052e7e6cfab19ee1c2ef4f69\",\"to\":\"0x19e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"contractAddress\":null,\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x7\",\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"address\":\"0x2ac3c1d3e24b45c6c310534bc2dd84b5ed576335\",\"data\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x00000000000000000000000019e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"0x00000000000000000000000028078300a459a9e136f872285654cdc74463041e\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x7\",\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"address\":\"0x19e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"data\":\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007735940000000000000000000000000000000000000000000000000000000000000000000\",\"topics\":[\"0x950494fc3642fae5221b6c32e0e45765c95ebb382a04a71b160db0843e74c99f\",\"0x0000000000000000000000000afe0a94415e8974052e7e6cfab19ee1c2ef4f69\",\"0x00000000000000000000000028078300a459a9e136f872285654cdc74463041e\",\"0x0000000000000000000000000afe0a94415e8974052e7e6cfab19ee1c2ef4f69\"]}],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000020000000000000800000000000000000000400000000000000000000000000000000000000002000000000000000000000000008000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000812000000000000000000000000000001000000000000000000000008000400008000000000000000000000000000000000000000000000000000000000800000000000000000000002000000000000000000000000000000000000100000000000000000002000000000000000000000000010000000000000000000000400000000020000\",\"status\":\"0x1\",\"type\":\"0x0\"}")]
+ new ResultWrapper<OptimismReceiptForRpc?> eth_getTransactionReceipt([JsonRpcParameter(ExampleValue = "[\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\"]")] Hash256 txHashData);
+
+ [JsonRpcMethod(IsImplemented = true,
+ Description = "Retrieves a transaction by hash",
+ IsSharable = true,
+ ExampleResponse = "{\"hash\":\"0xabca23910646013d608ec671de099447ab60b2b7159ad8319c3c088e8d9ea0fa\",\"nonce\":\"0x1a\",\"blockHash\":\"0xcb6756f69e0469acd5e5bb77966be580786ec2c11de85c9ddfd75257010e34f8\",\"blockNumber\":\"0x4dfbc7\",\"transactionIndex\":\"0xb\",\"from\":\"0xe1e7ab1c643dbe5b24739fdf2a5c7c193b54dd99\",\"to\":\"0x0b10e304088b2ba2b2acfd2f72573faad31a13a5\",\"value\":\"0x0\",\"gasPrice\":\"0x2540be400\",\"gas\":\"0xb4a4\",\"data\":\"0x095ea7b300000000000000000000000092c1576845703089cf6c0788379ed81f75f45dd500000000000000000000000000000000000000000000000000000002540be400\",\"input\":\"0x095ea7b300000000000000000000000092c1576845703089cf6c0788379ed81f75f45dd500000000000000000000000000000000000000000000000000000002540be400\",\"type\":\"0x0\",\"v\":\"0x2d\",\"s\":\"0x496d72d435ead8a8a9a865b14d6a102c1a9f848681d050dbbf11c522c612235\",\"r\":\"0xc8350e831203fecc8bff41f5cf858ac1d121e4b4d9e59c1137cc9440516ca9fd\"}")]
+ new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByHash(
+ [JsonRpcParameter(ExampleValue = "\"0xabca23910646013d608ec671de099447ab60b2b7159ad8319c3c088e8d9ea0fa\"")] Hash256 transactionHash);
+
+ [JsonRpcMethod(IsImplemented = true,
+ Description = "Retrieves a transaction by block hash and index",
+ IsSharable = true,
+ ExampleResponse = "{\"hash\":\"0xb87ec4c8cb36a06f49cdd93c2e9f63e0b7db9af07a605c8bcf1fbe705162344e\",\"nonce\":\"0x5d\",\"blockHash\":\"0xfe47fb3539ccce9d19a032473effdd6ce19e3c921bbae2746152ccf82ceef48e\",\"blockNumber\":\"0x4dfc90\",\"transactionIndex\":\"0x2\",\"from\":\"0xaa9a0f962e433755c843175488fe088fccf8526f\",\"to\":\"0x074b24cef703f17fe123fa1b82081055775b7004\",\"value\":\"0x0\",\"gasPrice\":\"0x2540be401\",\"gas\":\"0x130ab\",\"data\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"input\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"type\":\"0x0\",\"v\":\"0x2e\",\"s\":\"0x696f6db060a6dd30435a7f592506ba3213f81cf4704e211a1a45a99f8984189a\",\"r\":\"0x7e07076186e38b68cb7e4f68a04258a5744c5a2ad1a7153456ee662a07902954\"}")]
+ new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockHashAndIndex(
+ [JsonRpcParameter(ExampleValue = "[\"0xfe47fb3539ccce9d19a032473effdd6ce19e3c921bbae2746152ccf82ceef48e\",\"0x2\"]")] Hash256 blockHash, UInt256 positionIndex);
+
+ [JsonRpcMethod(IsImplemented = true,
+ Description = "Retrieves a transaction by block number and index",
+ IsSharable = true,
+ ExampleResponse = "{\"hash\":\"0xfd320a4949990929f64b52041c58a74c8ce13289b3d6853bd8073b0580aa031a\",\"nonce\":\"0x5b\",\"blockHash\":\"0xd779e1a5ce8f34544d66d219bb3e5331a7b280fae89a36d7d52813a23e1ca1e3\",\"blockNumber\":\"0x4dfdd8\",\"transactionIndex\":\"0x8\",\"from\":\"0xadb540569e2db497bd973c141b0b63be98461e40\",\"to\":\"0x074b24cef703f17fe123fa1b82081055775b7004\",\"value\":\"0x0\",\"gasPrice\":\"0x12a05f200\",\"gas\":\"0x927c0\",\"data\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"input\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"type\":\"0x0\",\"v\":\"0x2e\",\"s\":\"0x37b90a929884787df717c87258f0434e2f115ce2fbb4bfc230322112fa9d5bbc\",\"r\":\"0x5222eff9e16b5c3e9e8901d9c45fc8e0f9cf774e8a56546a504025ef67ceefec\"}")]
+ new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockNumberAndIndex(
+ [JsonRpcParameter(ExampleValue = "[\"5111256\",\"0x8\"]")] BlockParameter blockParameter, UInt256 positionIndex);
+}
(new)
+66
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9d0bf66a565574258c4a5fc1100411326ff8fbb7
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Core.Crypto;
+using Nethermind.JsonRpc;
+using Nethermind.Merge.Plugin;
+using Nethermind.Merge.Plugin.Data;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismEngineRpcModule : IOptimismEngineRpcModule
+{
+ private readonly IEngineRpcModule _engineRpcModule;
+
+ public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null)
+ {
+ return await _engineRpcModule.engine_forkchoiceUpdatedV1(forkchoiceState, payloadAttributes);
+ }
+
+ public Task<ResultWrapper<ExecutionPayload?>> engine_getPayloadV1(byte[] payloadId)
+ {
+ return _engineRpcModule.engine_getPayloadV1(payloadId);
+ }
+
+ public Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayload executionPayload)
+ {
+ return _engineRpcModule.engine_newPayloadV1(executionPayload);
+ }
+
+ public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV2(ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null)
+ {
+ return await _engineRpcModule.engine_forkchoiceUpdatedV2(forkchoiceState, payloadAttributes);
+ }
+
+ public Task<ResultWrapper<GetPayloadV2Result?>> engine_getPayloadV2(byte[] payloadId)
+ {
+ return _engineRpcModule.engine_getPayloadV2(payloadId);
+ }
+
+ public Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV2(ExecutionPayload executionPayload)
+ {
+ return _engineRpcModule.engine_newPayloadV2(executionPayload);
+ }
+
+ public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV3(ForkchoiceStateV1 forkchoiceState, OptimismPayloadAttributes? payloadAttributes = null)
+ {
+ return await _engineRpcModule.engine_forkchoiceUpdatedV3(forkchoiceState, payloadAttributes);
+ }
+
+ public async Task<ResultWrapper<OptimismGetPayloadV3Result?>> engine_getPayloadV3(byte[] payloadId)
+ {
+ ResultWrapper<GetPayloadV3Result?> result = await _engineRpcModule.engine_getPayloadV3(payloadId);
+ return ResultWrapper<OptimismGetPayloadV3Result?>.From(result, result.Data is null ? null : new OptimismGetPayloadV3Result(result.Data));
+ }
+
+ public Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV3(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot)
+ {
+ return _engineRpcModule.engine_newPayloadV3(executionPayload, blobVersionedHashes, parentBeaconBlockRoot);
+ }
+
+ public OptimismEngineRpcModule(IEngineRpcModule engineRpcModule)
+ {
+ _engineRpcModule = engineRpcModule;
+ }
+}
(new)
+91
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6d575a75d0ce6f31f0c828c38ff363e0ee71aa3d
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Core.Specs;
+using Nethermind.Facade;
+using Nethermind.Facade.Eth;
+using Nethermind.JsonRpc.Modules.Eth.GasPrice;
+using Nethermind.JsonRpc.Modules.Eth.FeeHistory;
+using Nethermind.Logging;
+using Nethermind.State;
+using Nethermind.TxPool;
+using Nethermind.Wallet;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.Blockchain.Find;
+using Nethermind.Core;
+using Nethermind.Crypto;
+using Nethermind.JsonRpc.Client;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismEthModuleFactory(
+ IJsonRpcConfig rpcConfig,
+ IBlockchainBridgeFactory blockchainBridgeFactory,
+ IBlockFinder blockFinder,
+ IReceiptFinder receiptFinder,
+ IStateReader stateReader,
+ ITxPool txPool,
+ ITxSender txSender,
+ IWallet wallet,
+ ILogManager logManager,
+ ISpecProvider specProvider,
+ IGasPriceOracle gasPriceOracle,
+ IEthSyncingInfo ethSyncingInfo,
+ IFeeHistoryOracle feeHistoryOracle,
+ ulong? secondsPerSlot,
+
+ IJsonRpcClient? sequencerRpcClient,
+ IAccountStateProvider accountStateProvider,
+ IEthereumEcdsa ecdsa,
+ ITxSealer sealer,
+ IOptimismSpecHelper opSpecHelper
+ )
+ : ModuleFactoryBase<IOptimismEthRpcModule>
+{
+ private readonly ILogManager _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager));
+ private readonly IStateReader _stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader));
+ private readonly IBlockchainBridgeFactory _blockchainBridgeFactory = blockchainBridgeFactory ?? throw new ArgumentNullException(nameof(blockchainBridgeFactory));
+ private readonly ITxPool _txPool = txPool ?? throw new ArgumentNullException(nameof(txPool));
+ private readonly ITxSender _txSender = txSender ?? throw new ArgumentNullException(nameof(txSender));
+ private readonly IWallet _wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
+ private readonly IJsonRpcConfig _rpcConfig = rpcConfig ?? throw new ArgumentNullException(nameof(rpcConfig));
+ private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider));
+ private readonly IGasPriceOracle _gasPriceOracle = gasPriceOracle ?? throw new ArgumentNullException(nameof(gasPriceOracle));
+ private readonly IEthSyncingInfo _ethSyncingInfo = ethSyncingInfo ?? throw new ArgumentNullException(nameof(ethSyncingInfo));
+ private readonly IFeeHistoryOracle _feeHistoryOracle = feeHistoryOracle ?? throw new ArgumentNullException(nameof(feeHistoryOracle));
+ private readonly IAccountStateProvider _accountStateProvider = accountStateProvider ?? throw new ArgumentNullException(nameof(accountStateProvider));
+ private readonly IEthereumEcdsa _ecdsa = ecdsa ?? throw new ArgumentNullException(nameof(ecdsa));
+ private readonly ITxSealer _sealer = sealer ?? throw new ArgumentNullException(nameof(sealer));
+ private readonly IBlockFinder _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder));
+ private readonly IReceiptFinder _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder));
+ private readonly IOptimismSpecHelper _opSpecHelper = opSpecHelper ?? throw new ArgumentNullException(nameof(opSpecHelper));
+
+ public override IOptimismEthRpcModule Create()
+ {
+ return new OptimismEthRpcModule(
+ _rpcConfig,
+ _blockchainBridgeFactory.CreateBlockchainBridge(),
+ _blockFinder,
+ _receiptFinder,
+ _stateReader,
+ _txPool,
+ _txSender,
+ _wallet,
+ _logManager,
+ _specProvider,
+ _gasPriceOracle,
+ _ethSyncingInfo,
+ _feeHistoryOracle,
+ secondsPerSlot,
+
+ sequencerRpcClient,
+ _accountStateProvider,
+ _ecdsa,
+ _sealer,
+ _opSpecHelper
+ );
+ }
+}
(new)
+260
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8a3d55863917e9dc667bda699d8390272c1af411
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs
@@ -0,0 +1,260 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Linq;
+using System.Threading.Tasks;
+using Nethermind.Blockchain.Find;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Crypto;
+using Nethermind.Evm;
+using Nethermind.Facade;
+using Nethermind.Facade.Eth;
+using Nethermind.Int256;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Client;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.JsonRpc.Modules.Eth;
+using Nethermind.JsonRpc.Modules.Eth.FeeHistory;
+using Nethermind.JsonRpc.Modules.Eth.GasPrice;
+using Nethermind.Logging;
+using Nethermind.Serialization.Rlp;
+using Nethermind.State;
+using Nethermind.Synchronization.ParallelSync;
+using Nethermind.TxPool;
+using Nethermind.Wallet;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismEthRpcModule : EthRpcModule, IOptimismEthRpcModule
+{
+ private readonly IJsonRpcClient? _sequencerRpcClient;
+ private readonly IAccountStateProvider _accountStateProvider;
+ private readonly IEthereumEcdsa _ecdsa;
+ private readonly ITxSealer _sealer;
+ private readonly IOptimismSpecHelper _opSpecHelper;
+
+ public OptimismEthRpcModule(
+ IJsonRpcConfig rpcConfig,
+ IBlockchainBridge blockchainBridge,
+ IBlockFinder blockFinder,
+ IReceiptFinder receiptFinder,
+ IStateReader stateReader,
+ ITxPool txPool,
+ ITxSender txSender,
+ IWallet wallet,
+ ILogManager logManager,
+ ISpecProvider specProvider,
+ IGasPriceOracle gasPriceOracle,
+ IEthSyncingInfo ethSyncingInfo,
+ IFeeHistoryOracle feeHistoryOracle,
+ ulong? secondsPerSlot,
+
+ IJsonRpcClient? sequencerRpcClient,
+ IAccountStateProvider accountStateProvider,
+ IEthereumEcdsa ecdsa,
+ ITxSealer sealer,
+ IOptimismSpecHelper opSpecHelper) : base(
+ rpcConfig,
+ blockchainBridge,
+ blockFinder,
+ receiptFinder,
+ stateReader,
+ txPool,
+ txSender,
+ wallet,
+ logManager,
+ specProvider,
+ gasPriceOracle,
+ ethSyncingInfo,
+ feeHistoryOracle,
+ secondsPerSlot)
+ {
+ _sequencerRpcClient = sequencerRpcClient;
+ _accountStateProvider = accountStateProvider;
+ _ecdsa = ecdsa;
+ _sealer = sealer;
+ _opSpecHelper = opSpecHelper;
+ }
+
+ public new ResultWrapper<OptimismReceiptForRpc[]?> eth_getBlockReceipts(BlockParameter blockParameter)
+ {
+ static ResultWrapper<OptimismReceiptForRpc[]?> GetBlockReceipts(IReceiptFinder receiptFinder, BlockParameter blockParameter, IBlockFinder blockFinder, ISpecProvider specProvider, IOptimismSpecHelper opSpecHelper)
+ {
+ SearchResult<Block> searchResult = blockFinder.SearchForBlock(blockParameter);
+ if (searchResult.IsError)
+ {
+ return ResultWrapper<OptimismReceiptForRpc[]?>.Success(null);
+ }
+
+ Block? block = searchResult.Object!;
+ OptimismTxReceipt[] receipts = receiptFinder.Get(block).Cast<OptimismTxReceipt>().ToArray() ?? new OptimismTxReceipt[block.Transactions.Length];
+ bool isEip1559Enabled = specProvider.GetSpec(block.Header).IsEip1559Enabled;
+
+ L1BlockGasInfo l1BlockGasInfo = new(block, opSpecHelper);
+
+ OptimismReceiptForRpc[]? result = [.. receipts
+ .Zip(block.Transactions, (r, t) =>
+ {
+ return new OptimismReceiptForRpc(t.Hash!, r, t.GetGasInfo(isEip1559Enabled, block.Header), l1BlockGasInfo.GetTxGasInfo(t), receipts.GetBlockLogFirstIndex(r.Index));
+ })];
+ return ResultWrapper<OptimismReceiptForRpc[]?>.Success(result);
+ }
+
+ return GetBlockReceipts(_receiptFinder, blockParameter, _blockFinder, _specProvider, _opSpecHelper);
+ }
+
+ public override async Task<ResultWrapper<Hash256>> eth_sendTransaction(TransactionForRpc rpcTx)
+ {
+ Transaction tx = rpcTx.ToTransactionWithDefaults(_blockchainBridge.GetChainId());
+ tx.SenderAddress ??= _ecdsa.RecoverAddress(tx);
+
+ if (tx.SenderAddress is null)
+ {
+ return ResultWrapper<Hash256>.Fail("Failed to recover sender");
+ }
+
+ if (rpcTx.Nonce is null)
+ {
+ tx.Nonce = _accountStateProvider.GetNonce(tx.SenderAddress);
+ }
+
+ await _sealer.Seal(tx, TxHandlingOptions.None);
+
+ return await eth_sendRawTransaction(Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes);
+ }
+
+ public override async Task<ResultWrapper<Hash256>> eth_sendRawTransaction(byte[] transaction)
+ {
+ if (_sequencerRpcClient is null)
+ {
+ return await base.eth_sendRawTransaction(transaction);
+ }
+
+ Hash256? result = await _sequencerRpcClient.Post<Hash256>(nameof(eth_sendRawTransaction), transaction);
+ if (result is null)
+ {
+ return ResultWrapper<Hash256>.Fail("Failed to forward transaction");
+ }
+
+ return ResultWrapper<Hash256>.Success(result);
+ }
+
+ public new ResultWrapper<OptimismReceiptForRpc?> eth_getTransactionReceipt(Hash256 txHash)
+ {
+ (TxReceipt? receipt, TxGasInfo? gasInfo, int logIndexStart) = _blockchainBridge.GetReceiptAndGasInfo(txHash);
+ if (receipt is null || gasInfo is null)
+ {
+ return ResultWrapper<OptimismReceiptForRpc?>.Success(null);
+ }
+
+ SearchResult<Block> foundBlock = _blockFinder.SearchForBlock(new(receipt.BlockHash!));
+ if (foundBlock.Object is null)
+ {
+ return ResultWrapper<OptimismReceiptForRpc?>.Success(null);
+ }
+
+ Block block = foundBlock.Object;
+
+ L1BlockGasInfo l1GasInfo = new(block, _opSpecHelper);
+ return ResultWrapper<OptimismReceiptForRpc?>.Success(
+ new(txHash, (OptimismTxReceipt)receipt, gasInfo.Value, l1GasInfo.GetTxGasInfo(block.Transactions.First(tx => tx.Hash == txHash)), logIndexStart));
+ }
+
+ public new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByHash(Hash256 transactionHash)
+ {
+ (TxReceipt? receipt, Transaction? transaction, UInt256? baseFee) = _blockchainBridge.GetTransaction(transactionHash, checkTxnPool: true);
+ if (transaction is null)
+ {
+ return ResultWrapper<OptimismTransactionForRpc?>.Success(null);
+ }
+
+ RecoverTxSenderIfNeeded(transaction);
+ OptimismTransactionForRpc transactionModel = new(receipt?.BlockHash, receipt as OptimismTxReceipt, transaction, baseFee);
+ if (_logger.IsTrace) _logger.Trace($"eth_getTransactionByHash request {transactionHash}, result: {transactionModel.Hash}");
+ return ResultWrapper<OptimismTransactionForRpc?>.Success(transactionModel);
+ }
+
+ public new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockHashAndIndex(Hash256 blockHash,
+ UInt256 positionIndex)
+ {
+ SearchResult<Block> searchResult = _blockFinder.SearchForBlock(new BlockParameter(blockHash));
+ if (searchResult.IsError || searchResult.Object is null)
+ {
+ return GetFailureResult<OptimismTransactionForRpc?, Block>(searchResult, _ethSyncingInfo.SyncMode.HaveNotSyncedBodiesYet());
+ }
+
+ Block block = searchResult.Object;
+ if (positionIndex < 0 || positionIndex > block!.Transactions.Length - 1)
+ {
+ return ResultWrapper<OptimismTransactionForRpc?>.Fail("Position Index is incorrect", ErrorCodes.InvalidParams);
+ }
+
+ OptimismTxReceipt[] receipts = _receiptFinder.Get(block).Cast<OptimismTxReceipt>().ToArray();
+ Transaction transaction = block.Transactions[(int)positionIndex];
+ RecoverTxSenderIfNeeded(transaction);
+
+ OptimismTransactionForRpc transactionModel = new(block.Hash, receipts.FirstOrDefault(r => r.TxHash == transaction.Hash), transaction, block.BaseFeePerGas);
+
+ return ResultWrapper<OptimismTransactionForRpc?>.Success(transactionModel);
+ }
+
+ public new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockNumberAndIndex(BlockParameter blockParameter,
+ UInt256 positionIndex)
+ {
+ SearchResult<Block> searchResult = _blockFinder.SearchForBlock(blockParameter);
+
+ if (searchResult.IsError)
+ {
+ return GetFailureResult<OptimismTransactionForRpc?, Block>(searchResult, _ethSyncingInfo.SyncMode.HaveNotSyncedBodiesYet());
+ }
+
+ Block? block = searchResult.Object;
+ if (positionIndex < 0 || positionIndex > block!.Transactions.Length - 1)
+ {
+ return ResultWrapper<OptimismTransactionForRpc?>.Fail("Position Index is incorrect", ErrorCodes.InvalidParams);
+ }
+
+ OptimismTxReceipt[] receipts = _receiptFinder.Get(block).Cast<OptimismTxReceipt>().ToArray();
+
+ Transaction transaction = block.Transactions[(int)positionIndex];
+ RecoverTxSenderIfNeeded(transaction);
+
+ OptimismTransactionForRpc transactionModel = new(block.Hash, receipts.FirstOrDefault(r => r.TxHash == transaction.Hash), transaction, block.BaseFeePerGas);
+
+ if (_logger.IsDebug)
+ _logger.Debug(
+ $"eth_getTransactionByBlockNumberAndIndex request {blockParameter}, index: {positionIndex}, result: {transactionModel.Hash}");
+ return ResultWrapper<OptimismTransactionForRpc?>.Success(transactionModel);
+ }
+
+ protected override ResultWrapper<BlockForRpc?> GetBlock(BlockParameter blockParameter, bool returnFullTransactionObjects)
+ {
+ SearchResult<Block> searchResult = _blockFinder.SearchForBlock(blockParameter, true);
+ if (searchResult.IsError)
+ {
+ return GetFailureResult<BlockForRpc?, Block>(searchResult, _ethSyncingInfo.SyncMode.HaveNotSyncedBodiesYet());
+ }
+
+ Block? block = searchResult.Object;
+
+ if (block is null)
+ {
+ return ResultWrapper<BlockForRpc?>.Success(null);
+ }
+
+ BlockForRpc result = new BlockForRpc(block, false, _specProvider);
+
+ OptimismTxReceipt[] receipts = _receiptFinder.Get(block).Cast<OptimismTxReceipt>().ToArray();
+
+ if (returnFullTransactionObjects)
+ {
+ _blockchainBridge.RecoverTxSenders(block);
+ result.Transactions = result.Transactions.Select((hash, index) => new OptimismTransactionForRpc(block.Hash, receipts.FirstOrDefault(r => r.TxHash?.Equals(hash) ?? false), block.Transactions[index], block.BaseFeePerGas));
+ }
+
+ return ResultWrapper<BlockForRpc?>.Success(result);
+ }
+}
(new)
+32
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a03afce861ad4194fdb2f7b8c4a97f524b1f368
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core.Crypto;
+using Nethermind.Int256;
+using Nethermind.Merge.Plugin.Data;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismGetPayloadV3Result
+{
+ public OptimismGetPayloadV3Result(GetPayloadV3Result result)
+ {
+ ExecutionPayload = result.ExecutionPayload;
+ BlockValue = result.BlockValue;
+
+ BlobsBundle = result.BlobsBundle;
+ ParentBeaconBlockRoot = result.ExecutionPayload.ParentBeaconBlockRoot!;
+ ShouldOverrideBuilder = result.ShouldOverrideBuilder;
+ }
+
+ public UInt256 BlockValue { get; }
+ public ExecutionPayload ExecutionPayload { get; }
+
+ public BlobsBundleV1 BlobsBundle { get; }
+
+ public Hash256 ParentBeaconBlockRoot { get; set; }
+
+ public bool ShouldOverrideBuilder { get; }
+
+ public override string ToString() => $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}, BlobsBundle blobs count: {BlobsBundle.Blobs.Length}, ParentBeaconBlockRoot: {ParentBeaconBlockRoot}, ShouldOverrideBuilder {ShouldOverrideBuilder}}}";
+}
(new)
+130
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8db3156fcae1e03301031b63f752efd396e774f7
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs
@@ -0,0 +1,130 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using Nethermind.Consensus.Producers;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Serialization.Rlp;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismPayloadAttributes : PayloadAttributes
+{
+ private byte[][]? _encodedTransactions;
+
+ public byte[][]? Transactions
+ {
+ get { return _encodedTransactions; }
+ set
+ {
+ _encodedTransactions = value;
+ _transactions = null;
+ }
+ }
+ public bool NoTxPool { get; set; }
+ public long GasLimit { get; set; }
+ public override long? GetGasLimit() => GasLimit;
+ private int TransactionsLength => Transactions?.Length ?? 0;
+
+ private Transaction[]? _transactions;
+
+ /// <summary>
+ /// Decodes and returns an array of <see cref="Transaction"/> from <see cref="Transactions"/>.
+ /// </summary>
+ /// <returns>An RLP-decoded array of <see cref="Transaction"/>.</returns>
+ public Transaction[]? GetTransactions() => _transactions ??= Transactions?
+ .Select((t, i) =>
+ {
+ try
+ {
+ return Rlp.Decode<Transaction>(t, RlpBehaviors.SkipTypedWrapping);
+ }
+ catch (RlpException e)
+ {
+ throw new RlpException($"Transaction {i} is not valid", e);
+ }
+ }).ToArray();
+
+ /// <summary>
+ /// RLP-encodes and sets the transactions specified to <see cref="Transactions"/>.
+ /// </summary>
+ /// <param name="transactions">An array of transactions to encode.</param>
+ public void SetTransactions(params Transaction[] transactions)
+ {
+ Transactions = transactions
+ .Select(t => Rlp.Encode(t, RlpBehaviors.SkipTypedWrapping).Bytes)
+ .ToArray();
+ _transactions = transactions;
+ }
+
+ protected override int ComputePayloadIdMembersSize() =>
+ // Add NoTxPool + Txs + GasLimit
+ base.ComputePayloadIdMembersSize() + sizeof(bool) + Keccak.Size * TransactionsLength + sizeof(long);
+
+ protected override int WritePayloadIdMembers(BlockHeader parentHeader, Span<byte> inputSpan)
+ {
+ var offset = base.WritePayloadIdMembers(parentHeader, inputSpan);
+
+ inputSpan[offset] = NoTxPool ? (byte)1 : (byte)0;
+ offset += 1;
+
+ Transaction[]? transactions = GetTransactions();
+ if (transactions is not null)
+ {
+ foreach (Transaction tx in transactions)
+ {
+ tx.Hash!.Bytes.CopyTo(inputSpan.Slice(offset, Keccak.Size));
+ offset += Keccak.Size;
+ }
+ }
+
+ BinaryPrimitives.WriteInt64BigEndian(inputSpan.Slice(offset, sizeof(long)), GasLimit);
+ offset += sizeof(long);
+
+ return offset;
+ }
+
+ public override PayloadAttributesValidationResult Validate(ISpecProvider specProvider, int apiVersion,
+ [NotNullWhen(false)] out string? error)
+ {
+ if (GasLimit == 0)
+ {
+ error = "Gas Limit should not be zero";
+ return PayloadAttributesValidationResult.InvalidPayloadAttributes;
+ }
+
+ try
+ {
+ GetTransactions();
+ }
+ catch (RlpException e)
+ {
+ error = $"Error decoding transactions: {e}";
+ return PayloadAttributesValidationResult.InvalidPayloadAttributes;
+ }
+ return base.Validate(specProvider, apiVersion, out error);
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder($"{nameof(PayloadAttributes)} {{")
+ .Append($"{nameof(Timestamp)}: {Timestamp}, ")
+ .Append($"{nameof(PrevRandao)}: {PrevRandao}, ")
+ .Append($"{nameof(SuggestedFeeRecipient)}: {SuggestedFeeRecipient}, ")
+ .Append($"{nameof(GasLimit)}: {GasLimit}, ")
+ .Append($"{nameof(NoTxPool)}: {NoTxPool}, ")
+ .Append($"{nameof(Transactions)}: {Transactions?.Length ?? 0}");
+
+ if (Withdrawals is not null)
+ sb.Append($", {nameof(Withdrawals)} count: {Withdrawals.Length}");
+
+ sb.Append('}');
+ return sb.ToString();
+ }
+}
(new)
+66
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismReceiptForRpc.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismReceiptForRpc.cs
new file mode 100644
index 0000000000000000000000000000000000000000..edaa037432549e08ca10a6c446a7a67e511da130
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismReceiptForRpc.cs
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core.Crypto;
+using Nethermind.Evm;
+using Nethermind.Int256;
+using Nethermind.JsonRpc.Data;
+using System.Text.Json.Serialization;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismReceiptForRpc : ReceiptForRpc
+{
+ public OptimismReceiptForRpc(Hash256 txHash, OptimismTxReceipt receipt, TxGasInfo gasInfo, L1TxGasInfo l1GasInfo, int logIndexStart = 0) : base(
+ txHash, receipt, gasInfo, logIndexStart)
+ {
+ if (receipt.TxType == Core.TxType.DepositTx)
+ {
+ DepositNonce = receipt.DepositNonce;
+ DepositReceiptVersion = receipt.DepositReceiptVersion;
+ }
+ else
+ {
+ L1Fee = l1GasInfo.L1Fee;
+ L1GasUsed = l1GasInfo.L1GasUsed;
+ L1GasPrice = l1GasInfo.L1GasPrice;
+ L1FeeScalar = l1GasInfo.L1FeeScalar;
+
+ L1BaseFeeScalar = l1GasInfo.L1BaseFeeScalar;
+ L1BlobBaseFee = l1GasInfo.L1BlobBaseFee;
+ L1BlobBaseFeeScalar = l1GasInfo.L1BlobBaseFeeScalar;
+ }
+ }
+
+ // DepositTx related fields
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? DepositNonce { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? DepositReceiptVersion { get; set; }
+
+
+ // Regular tx fields
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1Fee { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1GasPrice { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1GasUsed { get; set; }
+
+ // Pre-ecotone field of a regular tx fields
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? L1FeeScalar { get; set; }
+
+ // Fjord fields
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1BaseFeeScalar { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1BlobBaseFee { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? L1BlobBaseFeeScalar { get; set; }
+}
(new)
+61
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8a1b5da4d2198c797c05375415bf3e47541220bc
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTraceModuleFactory.cs
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Blockchain;
+using Nethermind.Blockchain.Receipts;
+using Nethermind.Consensus;
+using Nethermind.Consensus.Processing;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Consensus.Validators;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Core.Specs;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Modules.Trace;
+using Nethermind.Logging;
+using Nethermind.State;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismTraceModuleFactory(
+ IWorldStateManager worldStateManager,
+ IBlockTree blockTree,
+ IJsonRpcConfig jsonRpcConfig,
+ IBlockPreprocessorStep recoveryStep,
+ IRewardCalculatorSource rewardCalculatorSource,
+ IReceiptStorage receiptFinder,
+ ISpecProvider specProvider,
+ IPoSSwitcher poSSwitcher,
+ ILogManager logManager,
+ IL1CostHelper l1CostHelper,
+ IOptimismSpecHelper opSpecHelper,
+ Create2DeployerContractRewriter contractRewriter,
+ IWithdrawalProcessor withdrawalProcessor) : TraceModuleFactory(
+ worldStateManager,
+ blockTree,
+ jsonRpcConfig,
+ recoveryStep,
+ rewardCalculatorSource,
+ receiptFinder,
+ specProvider,
+ poSSwitcher,
+ logManager)
+{
+ protected override ReadOnlyTxProcessingEnv CreateTxProcessingEnv() =>
+ new OptimismReadOnlyTxProcessingEnv(_worldStateManager, _blockTree, _specProvider, _logManager, l1CostHelper, opSpecHelper);
+
+ protected override ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor, IReadOnlyTxProcessingScope scope, IRewardCalculator rewardCalculator) => new OptimismReadOnlyChainProcessingEnv(
+ scope,
+ Always.Valid,
+ _recoveryStep,
+ rewardCalculator,
+ _receiptStorage,
+ _specProvider,
+ _blockTree,
+ _worldStateManager.GlobalStateReader,
+ _logManager,
+ opSpecHelper,
+ contractRewriter,
+ withdrawalProcessor,
+ transactionsExecutor);
+}
(new)
+29
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTransactionForRpc.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTransactionForRpc.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cb28429f3f6266c4335dd965e0747bbb0b52ef8c
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/OptimismTransactionForRpc.cs
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core.Crypto;
+using Nethermind.Core;
+using Nethermind.Facade.Eth;
+using Nethermind.Int256;
+using System.Text.Json.Serialization;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class OptimismTransactionForRpc : TransactionForRpc
+{
+ public OptimismTransactionForRpc(Hash256? blockHash, OptimismTxReceipt? receipt, Transaction transaction, UInt256? baseFee = null)
+ : base(blockHash, receipt?.BlockNumber, receipt?.Index, transaction, baseFee)
+ {
+ if (transaction.Type == TxType.DepositTx)
+ {
+ SourceHash = transaction.SourceHash;
+ Mint = transaction.Mint;
+ IsSystemTx = transaction.IsOPSystemTransaction ? true : null;
+ Nonce = receipt?.DepositNonce;
+ DepositReceiptVersion = receipt?.DepositReceiptVersion;
+ }
+ }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public UInt256? DepositReceiptVersion { get; set; }
+}
(new)
+122
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs
new file mode 100644
index 0000000000000000000000000000000000000000..04da5fca22e5cf5bf99d2396ef69badb1ab6597c
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs
@@ -0,0 +1,122 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Api;
+using Nethermind.Blockchain;
+using Nethermind.Config;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Init.Steps;
+using Nethermind.JsonRpc;
+using Nethermind.JsonRpc.Client;
+using Nethermind.JsonRpc.Modules;
+using Nethermind.JsonRpc.Modules.Eth;
+using Nethermind.JsonRpc.Modules.Eth.FeeHistory;
+using Nethermind.Logging;
+using Nethermind.TxPool;
+using Nethermind.Wallet;
+
+namespace Nethermind.Optimism.Rpc;
+
+public class RegisterOptimismRpcModules : RegisterRpcModules
+{
+ private readonly OptimismNethermindApi _api;
+ private readonly ILogger _logger;
+ private readonly IOptimismConfig _config;
+ private readonly IJsonRpcConfig _jsonRpcConfig;
+
+ public RegisterOptimismRpcModules(INethermindApi api) : base(api)
+ {
+ _api = (OptimismNethermindApi)api;
+ _config = _api.Config<IOptimismConfig>();
+ _logger = _api.LogManager.GetClassLogger();
+ _jsonRpcConfig = _api.Config<IJsonRpcConfig>();
+ }
+
+ protected override void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvider)
+ {
+ StepDependencyException.ThrowIfNull(_api.BlockTree);
+ StepDependencyException.ThrowIfNull(_api.ReceiptStorage);
+ StepDependencyException.ThrowIfNull(_api.StateReader);
+ StepDependencyException.ThrowIfNull(_api.TxPool);
+ StepDependencyException.ThrowIfNull(_api.TxSender);
+ StepDependencyException.ThrowIfNull(_api.Wallet);
+ StepDependencyException.ThrowIfNull(_api.EthSyncingInfo);
+ StepDependencyException.ThrowIfNull(_api.GasPriceOracle);
+ StepDependencyException.ThrowIfNull(_api.SpecHelper);
+ StepDependencyException.ThrowIfNull(_api.SpecProvider);
+ StepDependencyException.ThrowIfNull(_api.WorldState);
+ StepDependencyException.ThrowIfNull(_api.EthereumEcdsa);
+ StepDependencyException.ThrowIfNull(_api.Sealer);
+
+ if (_config.SequencerUrl is null && _logger.IsWarn)
+ {
+ _logger.Warn($"SequencerUrl is not set. Nethermind will behave as a Sequencer");
+ }
+
+ BasicJsonRpcClient? sequencerJsonRpcClient = _config.SequencerUrl is null
+ ? null
+ : new(new Uri(_config.SequencerUrl), _api.EthereumJsonSerializer, _api.LogManager);
+ ModuleFactoryBase<IEthRpcModule> ethModuleFactory = CreateEthModuleFactory();
+
+ ITxSigner txSigner = new WalletTxSigner(_api.Wallet, _api.SpecProvider.ChainId);
+ TxSealer sealer = new(txSigner, _api.Timestamper);
+
+ var feeHistoryOracle = new FeeHistoryOracle(_api.BlockTree, _api.ReceiptStorage, _api.SpecProvider);
+ _api.DisposeStack.Push(feeHistoryOracle);
+
+ ModuleFactoryBase<IOptimismEthRpcModule> optimismEthModuleFactory = new OptimismEthModuleFactory(
+ _jsonRpcConfig,
+ _api,
+ _api.BlockTree.AsReadOnly(),
+ _api.ReceiptStorage,
+ _api.StateReader,
+ _api.TxPool,
+ _api.TxSender,
+ _api.Wallet,
+ _api.LogManager,
+ _api.SpecProvider,
+ _api.GasPriceOracle,
+ _api.EthSyncingInfo,
+ feeHistoryOracle,
+ _api.ConfigProvider.GetConfig<IBlocksConfig>().SecondsPerSlot,
+
+ sequencerJsonRpcClient,
+ _api.WorldState,
+ _api.EthereumEcdsa,
+ sealer,
+ _api.SpecHelper);
+
+ rpcModuleProvider.RegisterBounded(optimismEthModuleFactory,
+ _jsonRpcConfig.EthModuleConcurrentInstances ?? Environment.ProcessorCount, _jsonRpcConfig.Timeout);
+ }
+
+ protected override void RegisterTraceRpcModule(IRpcModuleProvider rpcModuleProvider)
+ {
+ StepDependencyException.ThrowIfNull(_api.WorldStateManager);
+ StepDependencyException.ThrowIfNull(_api.BlockTree);
+ StepDependencyException.ThrowIfNull(_api.ReceiptStorage);
+ StepDependencyException.ThrowIfNull(_api.RewardCalculatorSource);
+ StepDependencyException.ThrowIfNull(_api.SpecProvider);
+ StepDependencyException.ThrowIfNull(_api.WorldState);
+ StepDependencyException.ThrowIfNull(_api.L1CostHelper);
+ StepDependencyException.ThrowIfNull(_api.SpecHelper);
+
+ OptimismTraceModuleFactory traceModuleFactory = new(
+ _api.WorldStateManager,
+ _api.BlockTree,
+ _jsonRpcConfig,
+ _api.BlockPreprocessor,
+ _api.RewardCalculatorSource,
+ _api.ReceiptStorage,
+ _api.SpecProvider,
+ _api.PoSSwitcher,
+ _api.LogManager,
+ _api.L1CostHelper,
+ _api.SpecHelper,
+ new Create2DeployerContractRewriter(_api.SpecHelper, _api.SpecProvider, _api.BlockTree),
+ new BlockProductionWithdrawalProcessor(new NullWithdrawalProcessor()));
+
+ rpcModuleProvider.RegisterBoundedByCpuCount(traceModuleFactory, _jsonRpcConfig.Timeout);
+ }
+}
Tests for the Optimism Plugin
+295
-0
This section shows the addition of the Nethermind.Optimism.Test directory and its contents.
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/GasCostTests.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/GasCostTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2ba9f8160bada2c30639f58acebab989ed3f975f
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/GasCostTests.cs
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections;
+using Nethermind.Int256;
+using NUnit.Framework;
+
+namespace Nethermind.Optimism.Test;
+
+public class GasCostTests
+{
+ [TestCaseSource(nameof(FjordL1CostCalculationTestCases))]
+ public UInt256 Fjord_l1cost_should_match(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar) =>
+ OPL1CostHelper.ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar, out _);
+
+ public static IEnumerable FjordL1CostCalculationTestCases
+ {
+ get
+ {
+ static TestCaseData MakeTestCase(string testCase, ulong result, ulong fastLzSize, ulong l1BaseFee, ulong blobBaseFee, ulong l1BaseFeeScalar, ulong l1BlobBaseFeeScalar)
+ {
+ return new TestCaseData(new UInt256(fastLzSize), new UInt256(l1BaseFee), new UInt256(blobBaseFee), new UInt256(l1BaseFeeScalar), new UInt256(l1BlobBaseFeeScalar))
+ {
+ ExpectedResult = new UInt256(result),
+ TestName = testCase
+ };
+ }
+
+ yield return MakeTestCase("Low compressed size", 3203000, 50, 1000000000, 10000000, 2, 3);
+ yield return MakeTestCase("Below minimal #1", 3203000, 150, 1000000000, 10000000, 2, 3);
+ yield return MakeTestCase("Below minimal #2", 3203000, 170, 1000000000, 10000000, 2, 3);
+ yield return MakeTestCase("Above minimal #1", 3217602, 171, 1000000000, 10000000, 2, 3);
+ yield return MakeTestCase("Above minimal #2", 3994602, 200, 1000000000, 10000000, 2, 3);
+ yield return MakeTestCase("Regular block #1", 2883950646753, 1044, 28549556977, 1, 7600, 862000);
+ }
+ }
+}
(new)
+28
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..bf83b032cc63c8002fa86e4f28e95426cbc009d2
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Nullable>enable</Nullable>
+ <NoWarn>$(NoWarn);NUnit1032</NoWarn>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="coverlet.collector">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="NSubstitute" />
+ <PackageReference Include="NUnit" />
+ <PackageReference Include="NUnit.Analyzers">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="NUnit3TestAdapter" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" />
+ <PackageReference Include="RichardSzalay.MockHttp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Nethermind.JsonRpc.Test\Nethermind.JsonRpc.Test.csproj" />
+ <ProjectReference Include="..\Nethermind.Optimism\Nethermind.Optimism.csproj" />
+ </ItemGroup>
+
+</Project>
(new)
+140
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..37e1a5e4df79337b43bf265c315a0db0cf5a4ecb
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Collections;
+using Nethermind.Core.Extensions;
+using Nethermind.Serialization.Rlp;
+using NUnit.Framework;
+
+namespace Nethermind.Optimism.Test;
+
+public class ReceiptDecoderTests
+{
+ [TestCaseSource(nameof(DepositTxReceiptsSerializationTestCases))]
+ public void Test_tx_network_form_receipts_properly_encoded_for_trie(byte[] rlp, bool includesNonce, bool includesVersion, bool shouldIncludeNonceAndVersionForTxTrie)
+ {
+ static OptimismTxReceipt TestNetworkEncodingRoundTrip(byte[] rlp, bool includesNonce, bool includesVersion)
+ {
+ OptimismReceiptMessageDecoder decoder = new();
+ OptimismTxReceipt decodedReceipt = decoder.Decode(new RlpStream(rlp), RlpBehaviors.SkipTypedWrapping);
+
+ RlpStream encodedRlp = new(decoder.GetLength(decodedReceipt, RlpBehaviors.SkipTypedWrapping));
+ decoder.Encode(encodedRlp, decodedReceipt, RlpBehaviors.SkipTypedWrapping);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedReceipt.DepositNonce, includesNonce ? Is.Not.Null : Is.Null);
+ Assert.That(decodedReceipt.DepositReceiptVersion, includesVersion ? Is.Not.Null : Is.Null);
+ Assert.That(rlp, Is.EqualTo(encodedRlp.Data.ToArray()));
+ });
+
+ return decodedReceipt;
+ }
+
+ static OptimismTxReceipt TestStorageEncodingRoundTrip(OptimismTxReceipt decodedReceipt, bool includesNonce, bool includesVersion)
+ {
+ OptimismCompactReceiptStorageDecoder decoder = new();
+
+ RlpStream encodedRlp = new(decoder.GetLength(decodedReceipt, RlpBehaviors.SkipTypedWrapping));
+ decoder.Encode(encodedRlp, decodedReceipt, RlpBehaviors.SkipTypedWrapping);
+ encodedRlp.Position = 0;
+
+ OptimismTxReceipt decodedStorageReceipt = decoder.Decode(encodedRlp, RlpBehaviors.SkipTypedWrapping);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedStorageReceipt.DepositNonce, includesNonce ? Is.Not.Null : Is.Null);
+ Assert.That(decodedStorageReceipt.DepositReceiptVersion, includesVersion ? Is.Not.Null : Is.Null);
+ });
+
+ Rlp.ValueDecoderContext valueDecoderCtx = new(encodedRlp.Data);
+ decodedStorageReceipt = decoder.Decode(ref valueDecoderCtx, RlpBehaviors.SkipTypedWrapping);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedStorageReceipt.DepositNonce, includesNonce ? Is.Not.Null : Is.Null);
+ Assert.That(decodedStorageReceipt.DepositReceiptVersion, includesVersion ? Is.Not.Null : Is.Null);
+ });
+
+ return decodedReceipt;
+ }
+
+ static void TestTrieEncoding(OptimismTxReceipt decodedReceipt, bool shouldIncludeNonceAndVersionForTxTrie)
+ {
+ OptimismReceiptTrieDecoder trieDecoder = new();
+ RlpStream encodedTrieRlp = new(trieDecoder.GetLength(decodedReceipt, RlpBehaviors.SkipTypedWrapping));
+
+ trieDecoder.Encode(encodedTrieRlp, decodedReceipt, RlpBehaviors.SkipTypedWrapping);
+ encodedTrieRlp.Position = 0;
+
+ OptimismTxReceipt decodedTrieReceipt = trieDecoder.Decode(encodedTrieRlp, RlpBehaviors.SkipTypedWrapping);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedTrieReceipt.DepositNonce, shouldIncludeNonceAndVersionForTxTrie ? Is.Not.Null : Is.Null);
+ Assert.That(decodedTrieReceipt.DepositReceiptVersion, shouldIncludeNonceAndVersionForTxTrie ? Is.Not.Null : Is.Null);
+ });
+ }
+
+ OptimismTxReceipt decodedReceipt = TestNetworkEncodingRoundTrip(rlp, includesNonce, includesVersion);
+ TestStorageEncodingRoundTrip(decodedReceipt, includesNonce, includesVersion);
+ TestTrieEncoding(decodedReceipt, shouldIncludeNonceAndVersionForTxTrie);
+ }
+
+
+ public static IEnumerable DepositTxReceiptsSerializationTestCases
+ {
+ get
+ {
+ yield return new TestCaseData(
+ Bytes.FromHexString("7ef901090182f9f5b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080"),
+ true,
+ false,
+ false
+ )
+ {
+ TestName = "1st OP Sepolia block receipt"
+ };
+
+ yield return new TestCaseData(
+ Bytes.FromHexString("0x7ef9010c0182b729b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0830154f4"),
+ true,
+ false,
+ false
+ )
+ {
+ TestName = "Regolith receipt"
+ };
+
+ yield return new TestCaseData(
+ Bytes.FromHexString("0x7ef903660183023676b9010000000000000000000000000000000000000000000000000000000000001000000000000000000080000000000000000001000000000000000000000000000204000200000004000000000000000000000000000000000000000000000000000100000000020000400000000000020800000000000000000000000008000000000000000000004000400000020000000001800000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000100000000000002100000000000000000000020001000100000040000000000000000000000000000000000000000000008000000f9025af9011d944200000000000000000000000000000000000010f884a0b0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000a000000000000000000000000072fb15f502af58765015972a85f2c58551ef3fa1b88000000000000000000000000072fb15f502af58765015972a85f2c58551ef3fa1000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000f8dc944200000000000000000000000000000000000010f863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da000000000000000000000000072fb15f502af58765015972a85f2c58551ef3fa1a000000000000000000000000072fb15f502af58765015972a85f2c58551ef3fa1b860000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a944200000000000000000000000000000000000007f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0c056e47e441542720e5a953ab9fcf1cc3de86fb1d3078293fb9708e6e77816938080"),
+ true,
+ false,
+ false
+ )
+ {
+ TestName = "Regolith receipt 2"
+ };
+
+ yield return new TestCaseData(
+ Bytes.FromHexString("0xf901090183011711b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"),
+ false,
+ false,
+ false
+ )
+ {
+ TestName = "Regolith receipt of a regular tx"
+ };
+
+ yield return new TestCaseData(
+ Bytes.FromHexString("7ef9010d0182ab7bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c083b2557501"),
+ true,
+ true,
+ true
+ )
+ {
+ TestName = "Canyon receipt"
+ };
+ }
+ }
+}
(new)
+90
-0
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ac1a02e3452527a4b0668e22187f217a296a8362
--- /dev/null
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Threading.Tasks;
+using Nethermind.Blockchain.Synchronization;
+using Nethermind.Config;
+using Nethermind.Core;
+using Nethermind.Core.Extensions;
+using Nethermind.Core.Test.Builders;
+using Nethermind.Crypto;
+using Nethermind.Facade;
+using Nethermind.Facade.Eth;
+using Nethermind.JsonRpc.Client;
+using Nethermind.JsonRpc.Modules.Eth.FeeHistory;
+using Nethermind.JsonRpc.Test.Modules;
+using Nethermind.Logging;
+using Nethermind.Optimism.Rpc;
+using Nethermind.Serialization.Rlp;
+using Nethermind.Synchronization.ParallelSync;
+using Nethermind.TxPool;
+using NSubstitute;
+using NUnit.Framework;
+
+namespace Nethermind.Optimism.Test.Rpc;
+
+public class OptimismEthRpcModuleTest
+{
+ [Test]
+ public async Task Sequencer_send_transaction_with_signature_will_not_try_to_sign()
+ {
+ IBlockchainBridge bridge = Substitute.For<IBlockchainBridge>();
+ ITxSender txSender = Substitute.For<ITxSender>();
+ txSender.SendTransaction(tx: Arg.Any<Transaction>(), txHandlingOptions: TxHandlingOptions.PersistentBroadcast)
+ .Returns(returnThis: (TestItem.KeccakA, AcceptTxResult.Accepted));
+
+ EthereumEcdsa ethereumEcdsa = new EthereumEcdsa(chainId: TestBlockchainIds.ChainId);
+ TestRpcBlockchain rpcBlockchain = await TestRpcBlockchain
+ .ForTest(sealEngineType: SealEngineType.Optimism)
+ .WithBlockchainBridge(bridge)
+ .WithTxSender(txSender)
+ .WithOptimismEthRpcModule(
+ sequencerRpcClient: null /* explicitly using null to behave as Sequencer */,
+ accountStateProvider: Substitute.For<IAccountStateProvider>(),
+ ecdsa: new OptimismEthereumEcdsa(ethereumEcdsa),
+ sealer: Substitute.For<ITxSealer>(),
+ opSpecHelper: Substitute.For<IOptimismSpecHelper>())
+ .Build();
+
+ Transaction tx = Build.A.Transaction
+ .Signed(ecdsa: ethereumEcdsa, privateKey: TestItem.PrivateKeyA)
+ .TestObject;
+ string serialized = await rpcBlockchain.TestEthRpc("eth_sendRawTransaction", Rlp.Encode(item: tx, behaviors: RlpBehaviors.None).Bytes.ToHexString());
+
+ await txSender.Received().SendTransaction(tx: Arg.Any<Transaction>(), txHandlingOptions: TxHandlingOptions.PersistentBroadcast);
+ Assert.That(actual: serialized, expression: Is.EqualTo(expected: $$"""{"jsonrpc":"2.0","result":"{{TestItem.KeccakA.Bytes.ToHexString(withZeroX: true)}}","id":67}"""));
+ }
+}
+
+internal static class TestRpcBlockchainExt
+{
+ public static TestRpcBlockchain.Builder<TestRpcBlockchain> WithOptimismEthRpcModule(
+ this TestRpcBlockchain.Builder<TestRpcBlockchain> @this,
+ IJsonRpcClient? sequencerRpcClient,
+ IAccountStateProvider accountStateProvider,
+ IEthereumEcdsa ecdsa,
+ ITxSealer sealer,
+ IOptimismSpecHelper opSpecHelper)
+ {
+ return @this.WithEthRpcModule(blockchain => new OptimismEthRpcModule(
+ blockchain.RpcConfig,
+ blockchain.Bridge,
+ blockchain.BlockFinder,
+ blockchain.ReceiptFinder,
+ blockchain.StateReader,
+ blockchain.TxPool,
+ blockchain.TxSender,
+ blockchain.TestWallet,
+ LimboLogs.Instance,
+ blockchain.SpecProvider,
+ blockchain.GasPriceOracle,
+ new EthSyncingInfo(blockchain.BlockTree, blockchain.ReceiptStorage, new SyncConfig(),
+ new StaticSelector(SyncMode.All), Substitute.For<ISyncProgressResolver>(), blockchain.LogManager),
+ blockchain.FeeHistoryOracle ??
+ new FeeHistoryOracle(blockchain.BlockTree, blockchain.ReceiptStorage, blockchain.SpecProvider),
+ new BlocksConfig().SecondsPerSlot,
+
+ sequencerRpcClient, accountStateProvider, ecdsa, sealer, opSpecHelper
+ ));
+ }
+}
Changes to Nethermind.sln
+2
-0
This section shows the changes made to the Nethermind solution file to add references to Optimism projects.
diff --git NethermindEth/nethermind/src/Nethermind/Nethermind.sln NethermindEth/nethermind/src/Nethermind/Nethermind.sln
index 3a52e920b6c8f352dcbe39d3bec37698679764e7..a0ac7f0e84ca8fb2d0fe49d0e8950c0047ab770f 100644
--- NethermindEth/nethermind/src/Nethermind/Nethermind.sln
+++ NethermindEth/nethermind/src/Nethermind/Nethermind.sln
@@ -201,6 +201,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Serialization.Ssz", "Nethermind.Serialization.Ssz\Nethermind.Serialization.Ssz.csproj", "{FD9E69EA-75DB-495B-A8DD-139D7A9D8C6D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Network.Contract", "Nethermind.Network.Contract\Nethermind.Network.Contract.csproj", "{AD151E35-4BBC-4A83-8F57-CC8665F6E007}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Optimism", "Nethermind.Optimism\Nethermind.Optimism.csproj", "{32E5D15A-F6A6-40EA-9F31-05FE9251F958}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Init.Snapshot", "Nethermind.Init.Snapshot\Nethermind.Init.Snapshot.csproj", "{AD09FBCB-5496-499B-9129-B6D139A65B6F}"
EndProject
@@ -211,6 +212,7 @@ Directory.Packages.props = Directory.Packages.props
nuget.config = nuget.config
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Optimism.Test", "Nethermind.Optimism.Test\Nethermind.Optimism.Test.csproj", "{2438958D-46EA-4A7E-B89F-29E069DA0CCA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signer", "Signer", "{89311B58-AF36-4956-883D-54531BC1D5A3}"
EndProject
Ignored changes
+0
-38
diff --git NethermindEth/nethermind/.github/workflows/update-no-op-plugin.yml NethermindEth/nethermind/.github/workflows/update-no-op-plugin.yml
deleted file mode 100644
index eb2d61b091210cc0388a128a38ccbcace131afd5..0000000000000000000000000000000000000000
--- NethermindEth/nethermind/.github/workflows/update-no-op-plugin.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: Remove OP Plugin
-
-on:
- push:
- branches:
- - master
-
-jobs:
- remove-op-plugin:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- - name: Create or checkout no-op-plugin branch
- run: git checkout -b no-op-plugin || git checkout no-op-plugin
-
- - name: Remove Nethermind.Optimism directory
- run: rm -rf src/Nethermind/Nethermind.Optimism
-
- - name: Remove Nethermind.Optimism.Test directory
- run: rm -rf src/Nethermind/Nethermind.Optimism.Test
-
- - name: Remove references from Nethermind.sln
- run: |
- sed -i '/Nethermind.Optimism/d' src/Nethermind/Nethermind.sln
- sed -i '/Nethermind.Optimism.Test/d' src/Nethermind/Nethermind.sln
-
- - name: Commit changes
- run: |
- git config --global user.name 'github-actions[bot]'
- git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- git add .
- git commit -m 'Remove Nethermind.Optimism and Nethermind.Optimism.Test directories and references from solution file' || echo "No changes to commit"
-
- - name: Push changes
- run: git push origin no-op-plugin || echo "Nothing to push"