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.

This section shows the addition of the Nethermind.Optimism directory and its contents.

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; } +}
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; + } +}
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); + } +}
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))); + } +}
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; + } +}
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); +}
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; } +}
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; +}
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>(); + } +}
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); + } +}
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); + } +}
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); + } +}
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); + } +}
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; } +}
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); + } +}
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); +}
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); +}
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; + } +}
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 + ); + } +}
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); + } +}
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}}}"; +}
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(); + } +}
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; } +}
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); +}
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; } +}
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); + } +}

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); + } + } +}
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>
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" + }; + } + } +}
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 + )); + } +}

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
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"