From 952400786d65f618161a146599fc3c5a702d4376 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 14 May 2026 16:54:06 +0200 Subject: [PATCH 1/8] feat(engine): support targetGasLimit in PayloadAttributesV4 Implements ethereum/execution-apis#796, which adds a required `targetGasLimit` field to `PayloadAttributesV4` so the CL can supply the per-FCU gas-limit target instead of relying on the EL's static configuration. - `PayloadAttributes.TargetGasLimit` is included in `ToString`, the payload-id hash input, version inference, and required-field validation for V4 attributes. - `IGasLimitCalculator` gains an overload accepting an optional target override; `TargetAdjustedGasLimitCalculator` honors it, taking precedence over `BlocksConfig.TargetBlockGasLimit`. `AuRaContractGasLimitOverride` forwards the override to its inner calculator. - `BlockProducerBase` and `TestingRpcModule` pass the per-call target into the calculator so the standard gas-limit adjustment applies. --- .../AuRaContractGasLimitOverride.cs | 5 ++- .../TargetAdjustedGasLimitCalculatorTests.cs | 39 +++++++++++++++++++ .../IGasLimitCalculator.cs | 11 ++++++ .../Producers/BlockProducerBase.cs | 2 +- .../Producers/PayloadAttributes.cs | 25 +++++++++++- .../TargetAdjustedGasLimitCalculator.cs | 6 ++- .../AuRaMergeEngineModuleTests.cs | 2 +- .../EngineModuleTests.V1.cs | 8 +++- .../EngineModuleTests.V6.cs | 13 +++++-- .../TestingRpcModuleTests.cs | 1 + .../TestingRpcModule.cs | 2 +- 11 files changed, 100 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index d3faa4aa7ea8..42a36380d589 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -37,7 +37,10 @@ private static UInt256 MinimalContractGasLimit private readonly IGasLimitCalculator _innerCalculator = innerCalculator ?? throw new ArgumentNullException(nameof(innerCalculator)); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - public long GetGasLimit(BlockHeader parentHeader) => GetGasLimitFromContract(parentHeader) ?? _innerCalculator.GetGasLimit(parentHeader); + public long GetGasLimit(BlockHeader parentHeader) => GetGasLimit(parentHeader, targetGasLimitOverride: null); + + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) => + GetGasLimitFromContract(parentHeader) ?? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); private long? GetGasLimitFromContract(BlockHeader parentHeader) { diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 4761d0e9ee83..41b85e4e76c2 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs @@ -48,6 +48,45 @@ public void Is_calculating_correct_gasLimit(long currentGasLimit, long targetGas actualValue.Should().Be(expectedGasLimit); } + // Config target would decrease, override pushes up — confirms override drives the direction. + [TestCase(30_000_000, 20_000_000, 50_000_000, 30029295)] + // Config target would increase, override pulls down — confirms override drives the direction. + [TestCase(30_000_000, 100_000_000, 20_000_000, 29970705)] + public void Override_takes_precedence_over_config_target(long currentGasLimit, long configTargetGasLimit, long overrideTargetGasLimit, long expectedGasLimit) + { + int blockNumber = 20_000_000; + TestSpecProvider specProvider = new(Prague.Instance); + TargetAdjustedGasLimitCalculator calculator = new(specProvider, + new BlocksConfig() + { + TargetBlockGasLimit = configTargetGasLimit + }); + BlockHeader header = Build.A.BlockHeader.WithNumber(blockNumber - 1).WithGasLimit(currentGasLimit).TestObject; + + long actualValue = calculator.GetGasLimit(header, overrideTargetGasLimit); + + actualValue.Should().Be(expectedGasLimit); + } + + [Test] + public void Null_override_falls_back_to_config_target() + { + const long currentGasLimit = 30_000_000; + const long configTargetGasLimit = 100_000_000; + TestSpecProvider specProvider = new(Prague.Instance); + TargetAdjustedGasLimitCalculator calculator = new(specProvider, + new BlocksConfig() + { + TargetBlockGasLimit = configTargetGasLimit + }); + BlockHeader header = Build.A.BlockHeader.WithNumber(20_000_000 - 1).WithGasLimit(currentGasLimit).TestObject; + + long withoutOverride = calculator.GetGasLimit(header); + long withNullOverride = calculator.GetGasLimit(header, targetGasLimitOverride: null); + + withNullOverride.Should().Be(withoutOverride); + } + [Test] public void DoesNot_go_below_minimum() { diff --git a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs index 1660ef75aa76..ffe23841b5be 100644 --- a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs @@ -8,5 +8,16 @@ namespace Nethermind.Consensus public interface IGasLimitCalculator { long GetGasLimit(BlockHeader parentHeader); + + /// + /// Computes the gas limit for the next block using + /// in place of any statically-configured target. Used to honor the per-FCU + /// targetGasLimit introduced in PayloadAttributesV4 (Amsterdam). + /// + /// + /// Implementations that do not support a per-call target override should ignore it and fall + /// back to . + /// + long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) => GetGasLimit(parentHeader); } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index 423679494011..4d40706d2c90 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -214,7 +214,7 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent), + payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent, (long?)payloadAttributes?.TargetGasLimit), timestamp, _blocksConfig.GetExtraDataBytes()) { diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 437f04686a8d..6fdeeea7d60e 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -28,6 +28,14 @@ public class PayloadAttributes public ulong? SlotNumber { get; set; } + /// + /// Target gas limit the CL wants the EL to aim for when building a local payload, as defined by + /// PayloadAttributesV4 (Amsterdam). The EL applies the standard gas-limit adjustment relative to + /// the parent block, so the resulting may differ from this + /// target. Replaces the static EL-side target configuration for V4+ FCUs. + /// + public ulong? TargetGasLimit { get; set; } + public virtual long? GetGasLimit() => null; public override string ToString() => ToString(string.Empty); @@ -54,6 +62,11 @@ public string ToString(string indentation) sb.Append($", {nameof(SlotNumber)}: {SlotNumber}"); } + if (TargetGasLimit is not null) + { + sb.Append($", {nameof(TargetGasLimit)}: {TargetGasLimit}"); + } + sb.Append('}'); return sb.ToString(); @@ -83,7 +96,8 @@ protected virtual int ComputePayloadIdMembersSize() => + Address.Size // suggested fee recipient + (Withdrawals is null ? 0 : Keccak.Size) // withdrawals root hash + (ParentBeaconBlockRoot is null ? 0 : Keccak.Size) // parent beacon block root - + (SlotNumber is null ? 0 : sizeof(ulong)); // slot number + + (SlotNumber is null ? 0 : sizeof(ulong)) // slot number + + (TargetGasLimit is null ? 0 : sizeof(ulong)); // target gas limit protected static string ComputePayloadId(Span inputSpan) { @@ -128,6 +142,12 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span position += sizeof(ulong); } + if (TargetGasLimit is not null) + { + BinaryPrimitives.WriteUInt64BigEndian(inputSpan.Slice(position, sizeof(ulong)), TargetGasLimit.Value); + position += sizeof(ulong); + } + return position; } @@ -214,6 +234,7 @@ public virtual PayloadAttributesValidationResult Validate( >= PayloadAttributesVersions.V2 when Withdrawals is null => $"{nameof(Withdrawals)} must be provided", >= PayloadAttributesVersions.V3 when ParentBeaconBlockRoot is null => $"{nameof(ParentBeaconBlockRoot)} must be provided", >= PayloadAttributesVersions.V4 when SlotNumber is null => $"{nameof(SlotNumber)} must be provided", + >= PayloadAttributesVersions.V4 when TargetGasLimit is null => $"{nameof(TargetGasLimit)} must be provided", _ => null }; } @@ -226,7 +247,7 @@ public static class PayloadAttributesExtensions public static int GetVersion(this PayloadAttributes executionPayload) => executionPayload switch { - { SlotNumber: not null } => PayloadAttributesVersions.V4, + { SlotNumber: not null } or { TargetGasLimit: not null } => PayloadAttributesVersions.V4, { ParentBeaconBlockRoot: not null } => PayloadAttributesVersions.V3, { Withdrawals: not null } => PayloadAttributesVersions.V2, _ => PayloadAttributesVersions.V1 diff --git a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs index 90153d6ff643..91e170315f9a 100644 --- a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs @@ -13,12 +13,14 @@ public class TargetAdjustedGasLimitCalculator(ISpecProvider? specProvider, IBloc private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly IBlocksConfig _blocksConfig = blocksConfig ?? throw new ArgumentNullException(nameof(blocksConfig)); - public long GetGasLimit(BlockHeader parentHeader) + public long GetGasLimit(BlockHeader parentHeader) => GetGasLimit(parentHeader, targetGasLimitOverride: null); + + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) { long parentGasLimit = parentHeader.GasLimit; long gasLimit = parentGasLimit; - long? targetGasLimit = _blocksConfig.TargetBlockGasLimit; + long? targetGasLimit = targetGasLimitOverride ?? _blocksConfig.TargetBlockGasLimit; long newBlockNumber = parentHeader.Number + 1; IReleaseSpec spec = _specProvider.GetSpec(newBlockNumber, parentHeader.Timestamp); // taking the parent timestamp is a temporary solution if (targetGasLimit is not null) diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index 4972b6fdde29..aefa6ee6179e 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs @@ -78,7 +78,7 @@ public override Task processing_block_should_serialize_valid_responses(string bl "0xec6f5611ce3652fefd669e8d7e6d63bd8cdefdcdfe9a0a44eb61355084831da4", "0xf382f220de54b57ac9355d4eeb114f9e6bc4d25e307cdac0347b43d5534ac68e", "0xb8a1a0780980ab4e20a46237a3c533af8cd0386cf4c74d05c8ec5e9bf5cbc482", - "0x2802e8a8c34cd1ea", + "0xb6ea0a60ac692530", _auraWithdrawalContractAddress)] public override async Task Should_process_block_as_expected_V6(string latestValidHash, string blockHash, string stateRoot, string payloadId, string? customWithdrawalContractAddress) => await base.Should_process_block_as_expected_V6(latestValidHash, blockHash, stateRoot, payloadId, customWithdrawalContractAddress); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index e9442d11ce30..5101aa7a0c25 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1927,6 +1927,7 @@ static PayloadAttributes Attrs( Withdrawal[]? withdrawals = null, Hash256? parentBeaconBlockRoot = null, ulong? slotNumber = null, + ulong? targetGasLimit = null, Action? mutate = null) { PayloadAttributes attrs = new() @@ -1937,6 +1938,7 @@ static PayloadAttributes Attrs( Withdrawals = withdrawals, ParentBeaconBlockRoot = parentBeaconBlockRoot, SlotNumber = slotNumber, + TargetGasLimit = targetGasLimit, }; mutate?.Invoke(attrs); return attrs; @@ -1954,8 +1956,10 @@ static TestCaseData InvalidFieldCase(IReleaseSpec spec, string method, PayloadAt yield return InvalidFieldCase(Paris.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), Attrs(mutate: a => a.SuggestedFeeRecipient = null), "FCUv1 SuggestedFeeRecipient null"); yield return InvalidFieldCase(Cancun.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV3), Attrs(parentBeaconBlockRoot: Keccak.Zero), "FCUv3 Withdrawals null"); - yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1), "FCUv4 Withdrawals null"); - yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], slotNumber: 1), "FCUv4 ParentBeaconBlockRoot null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1, targetGasLimit: 30_000_000), "FCUv4 Withdrawals null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], slotNumber: 1, targetGasLimit: 30_000_000), "FCUv4 ParentBeaconBlockRoot null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, targetGasLimit: 30_000_000), "FCUv4 SlotNumber null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1), "FCUv4 TargetGasLimit null"); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs index 94717aebfce8..b1e460fe127f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs @@ -40,7 +40,7 @@ public enum BalErrorKind SurplusReads, } - [TestCase("0xb54389c226c76c61de0a8ebea2fe74cb0119295d34b8c01d0897901867c41c63", "0x14c38ed94cf91d5323eb3aaa7ff6c64c4c059a0a898658fcbc37f9723c25e6b3", "0x8a792f3d13211724decede460a451cdac669b5aaae37a01c2110d9f3114bc8a2", "0xfe420b1626a1f16d")] + [TestCase("0xb54389c226c76c61de0a8ebea2fe74cb0119295d34b8c01d0897901867c41c63", "0x14c38ed94cf91d5323eb3aaa7ff6c64c4c059a0a898658fcbc37f9723c25e6b3", "0x8a792f3d13211724decede460a451cdac669b5aaae37a01c2110d9f3114bc8a2", "0x2dc87ccb57a65b07")] [NonParallelizable] public virtual async Task Should_process_block_as_expected_V6( string latestValidHash, @@ -56,6 +56,7 @@ public virtual async Task Should_process_block_as_expected_V6( Address feeRecipient = TestItem.AddressC; ulong timestamp = Timestamper.UnixTime.Seconds; const ulong slotNumber = 1; + ulong targetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit; var fcuState = new { headBlockHash = startingHead.ToString(), @@ -71,6 +72,7 @@ public virtual async Task Should_process_block_as_expected_V6( withdrawals, parentBeaconBLockRoot = Keccak.Zero, slotNumber = slotNumber.ToHexString(true), + targetGasLimit = targetGasLimit.ToHexString(true), }; object?[] parameters = [chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs)]; @@ -344,7 +346,8 @@ public virtual async Task GetPayloadV6_builds_block_with_BAL(string? customWithd SuggestedFeeRecipient = Address.Zero, ParentBeaconBlockRoot = Keccak.Zero, Withdrawals = [], - SlotNumber = 1 + SlotNumber = 1, + TargetGasLimit = (ulong)genesis.Header.GasLimit }; Transaction tx = Build.A.Transaction @@ -466,7 +469,8 @@ private async Task AddNewBlockV6(IEngineRpcModule rpcModule, SuggestedFeeRecipient = TestItem.AddressF, Withdrawals = [], ParentBeaconBlockRoot = TestItem.KeccakE, - SlotNumber = chain.BlockTree.Head!.SlotNumber + 1 + SlotNumber = chain.BlockTree.Head!.SlotNumber + 1, + TargetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit }; Hash256 currentHeadHash = chain.BlockTree.HeadHash; ForkchoiceStateV1 forkchoiceState = new(currentHeadHash, currentHeadHash, currentHeadHash); @@ -805,7 +809,8 @@ private static (Transaction tx, Transaction tx2, Transaction tx3, Withdrawal wit SuggestedFeeRecipient = TestItem.AddressE, ParentBeaconBlockRoot = Keccak.Zero, Withdrawals = [withdrawal], - SlotNumber = slotNumber + SlotNumber = slotNumber, + TargetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit }; ForkchoiceStateV1 fcuState = new(parentHash, parentHash, parentHash); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestingRpcModuleTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestingRpcModuleTests.cs index 4d19b28ceea2..aa69c7da7cac 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestingRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestingRpcModuleTests.cs @@ -287,6 +287,7 @@ public async Task Testing_commitBlockV1_fails_on_invalid_transaction_rlp() IGasLimitCalculator gasLimitCalculator = Substitute.For(); gasLimitCalculator.GetGasLimit(Arg.Any()).Returns(parentHeader.GasLimit); + gasLimitCalculator.GetGasLimit(Arg.Any(), Arg.Any()).Returns(parentHeader.GasLimit); IBlockchainProcessor blockchainProcessor = CreateBlockProcessor(processOverride, onProcess); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 53d5ad427866..b99b38184b38 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -199,7 +199,7 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent), + payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent, (long?)payloadAttributes.TargetGasLimit), payloadAttributes.Timestamp, extraData ?? []) { From e217c7270aa2f5de96ac006fd14fc1fb523ef037 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 14 May 2026 17:29:29 +0200 Subject: [PATCH 2/8] fix(engine): address review on targetGasLimit handling - Collapse `IGasLimitCalculator.GetGasLimit` to a single method with `long? targetGasLimitOverride = null` default; update all implementations (TargetAdjusted, AuRa, FollowOtherMiners, Optimism, Xdc, ManualGasLimitCalculator). - Reject `TargetGasLimit > long.MaxValue` in `PayloadAttributes.Validate` to prevent silent wrap of the `ulong? -> long?` narrowing. Cast sites in BlockProducerBase / TestingRpcModule now use a `checked` cast for defense-in-depth on paths that bypass validation. - AuRa contract override: when the contract returns a limit and the CL also supplies a `targetGasLimit`, log a warning so operators know the contract takes precedence; otherwise forward the override to the inner calculator. - Add focused unit tests for AuRa: contract wins (override ignored) and no-contract (override forwarded). Add a V4 validation case for `TargetGasLimit > Int64.MaxValue`. --- .../AuRaContractGasLimitOverrideTests.cs | 51 +++++++++++++++++++ .../AuRaContractGasLimitOverride.cs | 16 ++++-- .../ManualGasLimitCalculator.cs | 2 +- .../Nethermind.Consensus/FollowOtherMiners.cs | 2 +- .../IGasLimitCalculator.cs | 14 ++--- .../Producers/BlockProducerBase.cs | 2 +- .../Producers/PayloadAttributes.cs | 2 + .../TargetAdjustedGasLimitCalculator.cs | 4 +- .../EngineModuleTests.V1.cs | 1 + .../TestingRpcModule.cs | 2 +- .../OptimismGasLimitCalculator.cs | 2 +- .../Nethermind.Xdc/XdcGasLimitCalculator.cs | 4 +- 12 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index 20434a32f57c..5b254e2bc63f 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -15,8 +15,11 @@ using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs.ChainSpecStyle; +using NSubstitute; using NUnit.Framework; namespace Nethermind.AuRa.Test.Contract; @@ -66,6 +69,54 @@ public async Task can_validate_gas_limit_incorrect() expectedGasLimit.Should().Be(CorrectHeadGasLimit); } + [Test] + public void Override_is_ignored_when_contract_returns_a_limit() + { + const long contractGasLimit = 42_000_000; + IGasLimitCalculator inner = Substitute.For(); + AuRaContractGasLimitOverride calculator = new( + [StubContract(contractGasLimit)], + new AuRaContractGasLimitOverride.Cache(), + minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, + inner, + LimboLogs.Instance); + BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; + + long result = calculator.GetGasLimit(parent, targetGasLimitOverride: 50_000_000); + + result.Should().Be(contractGasLimit); + inner.DidNotReceiveWithAnyArgs().GetGasLimit(default!); + } + + [Test] + public void Override_is_forwarded_to_inner_when_no_contract_limit() + { + const long innerGasLimit = 31_000_000; + const long overrideTarget = 33_000_000; + IGasLimitCalculator inner = Substitute.For(); + inner.GetGasLimit(Arg.Any(), Arg.Any()).Returns(innerGasLimit); + AuRaContractGasLimitOverride calculator = new( + contracts: [], + new AuRaContractGasLimitOverride.Cache(), + minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, + inner, + LimboLogs.Instance); + BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; + + long result = calculator.GetGasLimit(parent, targetGasLimitOverride: overrideTarget); + + result.Should().Be(innerGasLimit); + inner.Received(1).GetGasLimit(parent, overrideTarget); + } + + private static IBlockGasLimitContract StubContract(long gasLimit) + { + IBlockGasLimitContract contract = Substitute.For(); + contract.ActivationBlock.Returns(0L); + contract.BlockGasLimit(Arg.Any()).Returns((UInt256)gasLimit); + return contract; + } + [Test] public async Task skip_validate_gas_limit_before_enabled() { diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index 42a36380d589..37cdab2a9dc2 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -37,10 +37,18 @@ private static UInt256 MinimalContractGasLimit private readonly IGasLimitCalculator _innerCalculator = innerCalculator ?? throw new ArgumentNullException(nameof(innerCalculator)); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - public long GetGasLimit(BlockHeader parentHeader) => GetGasLimit(parentHeader, targetGasLimitOverride: null); - - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) => - GetGasLimitFromContract(parentHeader) ?? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) + { + long? contractLimit = GetGasLimitFromContract(parentHeader); + if (contractLimit is not null + && targetGasLimitOverride is not null + && contractLimit != targetGasLimitOverride + && _logger.IsWarn) + { + _logger.Warn($"AuRa gas-limit contract ({contractLimit}) overrides CL targetGasLimit ({targetGasLimitOverride}) for block {parentHeader.Number + 1}."); + } + return contractLimit ?? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); + } private long? GetGasLimitFromContract(BlockHeader parentHeader) { diff --git a/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs index c5ef4bc8e317..32d38435735d 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs @@ -8,6 +8,6 @@ namespace Nethermind.Consensus.Test public class ManualGasLimitCalculator : IGasLimitCalculator { public long GasLimit { get; set; } - public long GetGasLimit(BlockHeader parentHeader) => GasLimit; + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => GasLimit; } } diff --git a/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs b/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs index 146f402ccfac..c014d7f1fbc4 100644 --- a/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs +++ b/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs @@ -10,7 +10,7 @@ public class FollowOtherMiners(ISpecProvider specProvider) : IGasLimitCalculator { private readonly ISpecProvider _specProvider = specProvider; - public long GetGasLimit(BlockHeader parentHeader) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) { long gasLimit = parentHeader.GasLimit; long newBlockNumber = parentHeader.Number + 1; diff --git a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs index ffe23841b5be..31fd8264ad15 100644 --- a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs @@ -7,17 +7,11 @@ namespace Nethermind.Consensus { public interface IGasLimitCalculator { - long GetGasLimit(BlockHeader parentHeader); - /// - /// Computes the gas limit for the next block using - /// in place of any statically-configured target. Used to honor the per-FCU - /// targetGasLimit introduced in PayloadAttributesV4 (Amsterdam). + /// Computes the gas limit for the next block. When is supplied + /// (e.g. from PayloadAttributesV4.targetGasLimit introduced in Amsterdam), it takes precedence over + /// any statically-configured target. Implementations that don't honor a per-call target may ignore it. /// - /// - /// Implementations that do not support a per-call target override should ignore it and fall - /// back to . - /// - long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) => GetGasLimit(parentHeader); + long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null); } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index 4d40706d2c90..00d2a29045ec 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -214,7 +214,7 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent, (long?)payloadAttributes?.TargetGasLimit), + payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent, checked((long?)payloadAttributes?.TargetGasLimit)), timestamp, _blocksConfig.GetExtraDataBytes()) { diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 6fdeeea7d60e..4ebdbafae8ec 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -235,6 +235,8 @@ public virtual PayloadAttributesValidationResult Validate( >= PayloadAttributesVersions.V3 when ParentBeaconBlockRoot is null => $"{nameof(ParentBeaconBlockRoot)} must be provided", >= PayloadAttributesVersions.V4 when SlotNumber is null => $"{nameof(SlotNumber)} must be provided", >= PayloadAttributesVersions.V4 when TargetGasLimit is null => $"{nameof(TargetGasLimit)} must be provided", + // Internally gas limits are signed Int64; reject values that would overflow when narrowed. + >= PayloadAttributesVersions.V4 when TargetGasLimit > long.MaxValue => $"{nameof(TargetGasLimit)} must not exceed {long.MaxValue}", _ => null }; } diff --git a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs index 91e170315f9a..f347d622ae7a 100644 --- a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs @@ -13,9 +13,7 @@ public class TargetAdjustedGasLimitCalculator(ISpecProvider? specProvider, IBloc private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly IBlocksConfig _blocksConfig = blocksConfig ?? throw new ArgumentNullException(nameof(blocksConfig)); - public long GetGasLimit(BlockHeader parentHeader) => GetGasLimit(parentHeader, targetGasLimitOverride: null); - - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) { long parentGasLimit = parentHeader.GasLimit; long gasLimit = parentGasLimit; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 5101aa7a0c25..d24a5260abf0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1960,6 +1960,7 @@ static TestCaseData InvalidFieldCase(IReleaseSpec spec, string method, PayloadAt yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], slotNumber: 1, targetGasLimit: 30_000_000), "FCUv4 ParentBeaconBlockRoot null"); yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, targetGasLimit: 30_000_000), "FCUv4 SlotNumber null"); yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1), "FCUv4 TargetGasLimit null"); + yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1, targetGasLimit: (ulong)long.MaxValue + 1), "FCUv4 TargetGasLimit exceeds Int64"); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index b99b38184b38..8d4b056c5d16 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -199,7 +199,7 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent, (long?)payloadAttributes.TargetGasLimit), + payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent, checked((long?)payloadAttributes.TargetGasLimit)), payloadAttributes.Timestamp, extraData ?? []) { diff --git a/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs b/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs index 5e5a6932e95f..f6f1b696ada3 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs @@ -9,6 +9,6 @@ namespace Nethermind.Optimism; public class OptimismGasLimitCalculator : IGasLimitCalculator { - public long GetGasLimit(BlockHeader parentHeader) => + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => throw new InvalidOperationException("GasLimit in Optimism should come from payload attributes."); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs index a26413a092f0..559d2ffb52e3 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs @@ -12,12 +12,12 @@ namespace Nethermind.Xdc; internal class XdcGasLimitCalculator(ISpecProvider specProvider, IBlocksConfig blocksConfig) : IGasLimitCalculator { private readonly TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = new(specProvider, blocksConfig); - public long GetGasLimit(BlockHeader parentHeader) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) { IXdcReleaseSpec spec = specProvider.GetXdcSpec(parentHeader.Number + 1); if (spec.IsDynamicGasLimitBlock) { - return targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader); + return targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); } return blocksConfig.TargetBlockGasLimit ?? XdcConstants.DefaultTargetGasLimit; } From c3ae2b4f6c7fdc6fb6eadbdaf610dd4d725546d0 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 14 May 2026 17:57:42 +0200 Subject: [PATCH 3/8] refactor(engine): simplify targetGasLimit wiring; AuRa override wins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Type `TargetGasLimit` as `long?` directly (was `ulong?`), dropping the narrowing cast and the upper-bound `> long.MaxValue` validation. Gas limits in Nethermind are already stored as `long`. - Consolidate the parameterless `PayloadAttributes.GetGasLimit()` and the duplicated `payloadAttributes?` access at the call site into a single `GetGasLimit(BlockHeader parent, IGasLimitCalculator calc)`. Base implementation routes `TargetGasLimit` through the calculator; Boost/Optimism/Taiko override to return their unadjusted value. - AuRa: drop the conflict warning and flip precedence — the CL's per-FCU `targetGasLimit` now takes priority over the on-chain contract value, with the contract acting as fallback when no override is supplied. - Dedup `TargetAdjustedGasLimitCalculatorTests` and the new AuRa override tests around small construction helpers. --- .../AuRaContractGasLimitOverrideTests.cs | 43 +++++---- .../AuRaContractGasLimitOverride.cs | 18 ++-- .../TargetAdjustedGasLimitCalculatorTests.cs | 95 +++++++------------ .../Producers/BlockProducerBase.cs | 2 +- .../Producers/PayloadAttributes.cs | 17 ++-- .../EngineModuleTests.V1.cs | 3 +- .../EngineModuleTests.V6.cs | 8 +- .../Boost/BoostPayloadAttributes.cs | 5 +- .../TestingRpcModule.cs | 2 +- .../Rpc/OptimismPayloadAttributes.cs | 3 +- .../TaikoPayloadAttributes.cs | 4 +- 11 files changed, 91 insertions(+), 109 deletions(-) diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index 5b254e2bc63f..4cd134c33f46 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -70,22 +70,17 @@ public async Task can_validate_gas_limit_incorrect() } [Test] - public void Override_is_ignored_when_contract_returns_a_limit() + public void Override_takes_precedence_over_contract_limit() { - const long contractGasLimit = 42_000_000; - IGasLimitCalculator inner = Substitute.For(); - AuRaContractGasLimitOverride calculator = new( - [StubContract(contractGasLimit)], - new AuRaContractGasLimitOverride.Cache(), - minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, - inner, - LimboLogs.Instance); + const long innerGasLimit = 99_000_000; + const long overrideTarget = 50_000_000; + (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inner) = BuildOverride(contractGasLimit: 42_000_000, innerResult: innerGasLimit); BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; - long result = calculator.GetGasLimit(parent, targetGasLimitOverride: 50_000_000); + long result = calculator.GetGasLimit(parent, targetGasLimitOverride: overrideTarget); - result.Should().Be(contractGasLimit); - inner.DidNotReceiveWithAnyArgs().GetGasLimit(default!); + result.Should().Be(innerGasLimit); + inner.Received(1).GetGasLimit(parent, overrideTarget); } [Test] @@ -93,14 +88,7 @@ public void Override_is_forwarded_to_inner_when_no_contract_limit() { const long innerGasLimit = 31_000_000; const long overrideTarget = 33_000_000; - IGasLimitCalculator inner = Substitute.For(); - inner.GetGasLimit(Arg.Any(), Arg.Any()).Returns(innerGasLimit); - AuRaContractGasLimitOverride calculator = new( - contracts: [], - new AuRaContractGasLimitOverride.Cache(), - minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, - inner, - LimboLogs.Instance); + (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inner) = BuildOverride(contractGasLimit: null, innerResult: innerGasLimit); BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; long result = calculator.GetGasLimit(parent, targetGasLimitOverride: overrideTarget); @@ -109,6 +97,21 @@ public void Override_is_forwarded_to_inner_when_no_contract_limit() inner.Received(1).GetGasLimit(parent, overrideTarget); } + private static (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inner) BuildOverride(long? contractGasLimit, long innerResult) + { + IGasLimitCalculator inner = Substitute.For(); + inner.GetGasLimit(Arg.Any(), Arg.Any()).Returns(innerResult); + + IBlockGasLimitContract[] contracts = contractGasLimit is { } limit ? [StubContract(limit)] : []; + AuRaContractGasLimitOverride calculator = new( + contracts, + new AuRaContractGasLimitOverride.Cache(), + minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, + inner, + LimboLogs.Instance); + return (calculator, inner); + } + private static IBlockGasLimitContract StubContract(long gasLimit) { IBlockGasLimitContract contract = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index 37cdab2a9dc2..56b1fec98385 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -37,18 +37,12 @@ private static UInt256 MinimalContractGasLimit private readonly IGasLimitCalculator _innerCalculator = innerCalculator ?? throw new ArgumentNullException(nameof(innerCalculator)); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) - { - long? contractLimit = GetGasLimitFromContract(parentHeader); - if (contractLimit is not null - && targetGasLimitOverride is not null - && contractLimit != targetGasLimitOverride - && _logger.IsWarn) - { - _logger.Warn($"AuRa gas-limit contract ({contractLimit}) overrides CL targetGasLimit ({targetGasLimitOverride}) for block {parentHeader.Number + 1}."); - } - return contractLimit ?? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); - } + // CL-supplied targetGasLimit (PayloadAttributesV4) takes precedence over the on-chain + // contract value so validators can steer per-FCU; contract value is the fallback. + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => + targetGasLimitOverride is not null + ? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride) + : GetGasLimitFromContract(parentHeader) ?? _innerCalculator.GetGasLimit(parentHeader); private long? GetGasLimitFromContract(BlockHeader parentHeader) { diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 41b85e4e76c2..2667a17eeeb7 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs @@ -16,87 +16,62 @@ namespace Nethermind.Consensus.Test { public class TargetAdjustedGasLimitCalculatorTests { - [Test] - public void Is_bump_on_1559_eip_block() + private const long DefaultBlockNumber = 20_000_000; + + private static long Calc( + IReleaseSpec spec, + long parentGasLimit, + long? configTarget = null, + long? overrideTarget = null, + long blockNumber = DefaultBlockNumber) { - int londonBlock = 5; - long gasLimit = 1000000000000000000; - OverridableReleaseSpec spec = new(London.Instance) - { - Eip1559TransitionBlock = londonBlock - }; TestSpecProvider specProvider = new(spec); - TargetAdjustedGasLimitCalculator targetedAdjustedGasLimitCalculator = new(specProvider, new BlocksConfig()); - BlockHeader header = Build.A.BlockHeader.WithNumber(londonBlock - 1).WithGasLimit(gasLimit).TestObject; - long actualValue = targetedAdjustedGasLimitCalculator.GetGasLimit(header); - Assert.That(actualValue, Is.EqualTo(gasLimit * Eip1559Constants.DefaultElasticityMultiplier)); + TargetAdjustedGasLimitCalculator calculator = new( + specProvider, + new BlocksConfig { TargetBlockGasLimit = configTarget }); + BlockHeader header = Build.A.BlockHeader.WithNumber(blockNumber - 1).WithGasLimit(parentGasLimit).TestObject; + return calculator.GetGasLimit(header, overrideTarget); } - [TestCase(30_000_000, 100_000_000, 30029295)] - public void Is_calculating_correct_gasLimit(long currentGasLimit, long targetGasLimit, long expectedGasLimit) + [Test] + public void Is_bump_on_1559_eip_block() { - int blockNumber = 20_000_000; - long gasLimit = currentGasLimit; - TestSpecProvider specProvider = new(Prague.Instance); - TargetAdjustedGasLimitCalculator targetedAdjustedGasLimitCalculator = new(specProvider, - new BlocksConfig() - { - TargetBlockGasLimit = targetGasLimit - }); - BlockHeader header = Build.A.BlockHeader.WithNumber(blockNumber - 1).WithGasLimit(gasLimit).TestObject; - long actualValue = targetedAdjustedGasLimitCalculator.GetGasLimit(header); - actualValue.Should().Be(expectedGasLimit); + const int londonBlock = 5; + const long parentGasLimit = 1000000000000000000; + OverridableReleaseSpec spec = new(London.Instance) { Eip1559TransitionBlock = londonBlock }; + + Calc(spec, parentGasLimit, blockNumber: londonBlock) + .Should().Be(parentGasLimit * Eip1559Constants.DefaultElasticityMultiplier); } + [TestCase(30_000_000, 100_000_000, 30029295)] + public void Is_calculating_correct_gasLimit(long parentGasLimit, long configTarget, long expected) + => Calc(Prague.Instance, parentGasLimit, configTarget: configTarget).Should().Be(expected); + // Config target would decrease, override pushes up — confirms override drives the direction. [TestCase(30_000_000, 20_000_000, 50_000_000, 30029295)] // Config target would increase, override pulls down — confirms override drives the direction. [TestCase(30_000_000, 100_000_000, 20_000_000, 29970705)] - public void Override_takes_precedence_over_config_target(long currentGasLimit, long configTargetGasLimit, long overrideTargetGasLimit, long expectedGasLimit) - { - int blockNumber = 20_000_000; - TestSpecProvider specProvider = new(Prague.Instance); - TargetAdjustedGasLimitCalculator calculator = new(specProvider, - new BlocksConfig() - { - TargetBlockGasLimit = configTargetGasLimit - }); - BlockHeader header = Build.A.BlockHeader.WithNumber(blockNumber - 1).WithGasLimit(currentGasLimit).TestObject; - - long actualValue = calculator.GetGasLimit(header, overrideTargetGasLimit); - - actualValue.Should().Be(expectedGasLimit); - } + public void Override_takes_precedence_over_config_target(long parentGasLimit, long configTarget, long overrideTarget, long expected) + => Calc(Prague.Instance, parentGasLimit, configTarget, overrideTarget).Should().Be(expected); [Test] public void Null_override_falls_back_to_config_target() { - const long currentGasLimit = 30_000_000; - const long configTargetGasLimit = 100_000_000; - TestSpecProvider specProvider = new(Prague.Instance); - TargetAdjustedGasLimitCalculator calculator = new(specProvider, - new BlocksConfig() - { - TargetBlockGasLimit = configTargetGasLimit - }); - BlockHeader header = Build.A.BlockHeader.WithNumber(20_000_000 - 1).WithGasLimit(currentGasLimit).TestObject; - - long withoutOverride = calculator.GetGasLimit(header); - long withNullOverride = calculator.GetGasLimit(header, targetGasLimitOverride: null); + const long parentGasLimit = 30_000_000; + const long configTarget = 100_000_000; - withNullOverride.Should().Be(withoutOverride); + Calc(Prague.Instance, parentGasLimit, configTarget, overrideTarget: null) + .Should().Be(Calc(Prague.Instance, parentGasLimit, configTarget)); } [Test] public void DoesNot_go_below_minimum() { - int londonBlock = 5; - long gasLimit = 5000; - TestSpecProvider specProvider = new(London.Instance); - TargetAdjustedGasLimitCalculator targetedAdjustedGasLimitCalculator = new(specProvider, new BlocksConfig() { TargetBlockGasLimit = 1 }); - BlockHeader header = Build.A.BlockHeader.WithNumber(londonBlock - 1).WithGasLimit(gasLimit).TestObject; - long actualValue = targetedAdjustedGasLimitCalculator.GetGasLimit(header); - Assert.That(actualValue, Is.EqualTo(specProvider.GetSpec(new ForkActivation(5)).MinGasLimit)); + const int londonBlock = 5; + + Calc(London.Instance, parentGasLimit: 5000, configTarget: 1, blockNumber: londonBlock) + .Should().Be(London.Instance.MinGasLimit); } } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index 00d2a29045ec..14290546be12 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -214,7 +214,7 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent, checked((long?)payloadAttributes?.TargetGasLimit)), + payloadAttributes?.GetGasLimit(parent, GasLimitCalculator) ?? GasLimitCalculator.GetGasLimit(parent), timestamp, _blocksConfig.GetExtraDataBytes()) { diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 4ebdbafae8ec..44ef4225684f 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -34,9 +34,16 @@ public class PayloadAttributes /// the parent block, so the resulting may differ from this /// target. Replaces the static EL-side target configuration for V4+ FCUs. /// - public ulong? TargetGasLimit { get; set; } + public long? TargetGasLimit { get; set; } - public virtual long? GetGasLimit() => null; + /// + /// Resolves the gas limit for the block being built from these attributes. The default + /// implementation routes through + /// so the standard parent ± delta adjustment is applied. Subclasses that supply an unadjusted + /// value directly (e.g. Boost, Optimism, Taiko) override this. + /// + public virtual long GetGasLimit(BlockHeader parent, IGasLimitCalculator gasLimitCalculator) + => gasLimitCalculator.GetGasLimit(parent, TargetGasLimit); public override string ToString() => ToString(string.Empty); @@ -144,8 +151,8 @@ protected virtual int WritePayloadIdMembers(BlockHeader parentHeader, Span if (TargetGasLimit is not null) { - BinaryPrimitives.WriteUInt64BigEndian(inputSpan.Slice(position, sizeof(ulong)), TargetGasLimit.Value); - position += sizeof(ulong); + BinaryPrimitives.WriteInt64BigEndian(inputSpan.Slice(position, sizeof(long)), TargetGasLimit.Value); + position += sizeof(long); } return position; @@ -235,8 +242,6 @@ public virtual PayloadAttributesValidationResult Validate( >= PayloadAttributesVersions.V3 when ParentBeaconBlockRoot is null => $"{nameof(ParentBeaconBlockRoot)} must be provided", >= PayloadAttributesVersions.V4 when SlotNumber is null => $"{nameof(SlotNumber)} must be provided", >= PayloadAttributesVersions.V4 when TargetGasLimit is null => $"{nameof(TargetGasLimit)} must be provided", - // Internally gas limits are signed Int64; reject values that would overflow when narrowed. - >= PayloadAttributesVersions.V4 when TargetGasLimit > long.MaxValue => $"{nameof(TargetGasLimit)} must not exceed {long.MaxValue}", _ => null }; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index d24a5260abf0..3d33d664a586 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1927,7 +1927,7 @@ static PayloadAttributes Attrs( Withdrawal[]? withdrawals = null, Hash256? parentBeaconBlockRoot = null, ulong? slotNumber = null, - ulong? targetGasLimit = null, + long? targetGasLimit = null, Action? mutate = null) { PayloadAttributes attrs = new() @@ -1960,7 +1960,6 @@ static TestCaseData InvalidFieldCase(IReleaseSpec spec, string method, PayloadAt yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], slotNumber: 1, targetGasLimit: 30_000_000), "FCUv4 ParentBeaconBlockRoot null"); yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, targetGasLimit: 30_000_000), "FCUv4 SlotNumber null"); yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1), "FCUv4 TargetGasLimit null"); - yield return InvalidFieldCase(Amsterdam.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV4), Attrs(withdrawals: [], parentBeaconBlockRoot: Keccak.Zero, slotNumber: 1, targetGasLimit: (ulong)long.MaxValue + 1), "FCUv4 TargetGasLimit exceeds Int64"); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs index b1e460fe127f..e7370355c243 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V6.cs @@ -56,7 +56,7 @@ public virtual async Task Should_process_block_as_expected_V6( Address feeRecipient = TestItem.AddressC; ulong timestamp = Timestamper.UnixTime.Seconds; const ulong slotNumber = 1; - ulong targetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit; + long targetGasLimit = chain.BlockTree.Head!.GasLimit; var fcuState = new { headBlockHash = startingHead.ToString(), @@ -347,7 +347,7 @@ public virtual async Task GetPayloadV6_builds_block_with_BAL(string? customWithd ParentBeaconBlockRoot = Keccak.Zero, Withdrawals = [], SlotNumber = 1, - TargetGasLimit = (ulong)genesis.Header.GasLimit + TargetGasLimit = genesis.Header.GasLimit }; Transaction tx = Build.A.Transaction @@ -470,7 +470,7 @@ private async Task AddNewBlockV6(IEngineRpcModule rpcModule, Withdrawals = [], ParentBeaconBlockRoot = TestItem.KeccakE, SlotNumber = chain.BlockTree.Head!.SlotNumber + 1, - TargetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit + TargetGasLimit = chain.BlockTree.Head!.GasLimit }; Hash256 currentHeadHash = chain.BlockTree.HeadHash; ForkchoiceStateV1 forkchoiceState = new(currentHeadHash, currentHeadHash, currentHeadHash); @@ -810,7 +810,7 @@ private static (Transaction tx, Transaction tx2, Transaction tx3, Withdrawal wit ParentBeaconBlockRoot = Keccak.Zero, Withdrawals = [withdrawal], SlotNumber = slotNumber, - TargetGasLimit = (ulong)chain.BlockTree.Head!.GasLimit + TargetGasLimit = chain.BlockTree.Head!.GasLimit }; ForkchoiceStateV1 fcuState = new(parentHash, parentHash, parentHash); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostPayloadAttributes.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostPayloadAttributes.cs index a4e8e224568d..da23dc4fdfd5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostPayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostPayloadAttributes.cs @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus; using Nethermind.Consensus.Producers; +using Nethermind.Core; namespace Nethermind.Merge.Plugin.BlockProduction.Boost; @@ -9,5 +11,6 @@ public class BoostPayloadAttributes : PayloadAttributes { public long? GasLimit { get; set; } - public override long? GetGasLimit() => GasLimit; + public override long GetGasLimit(BlockHeader parent, IGasLimitCalculator gasLimitCalculator) + => GasLimit ?? base.GetGasLimit(parent, gasLimitCalculator); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs index 8d4b056c5d16..0aa7a4b87e24 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/TestingRpcModule.cs @@ -199,7 +199,7 @@ private BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes pay blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes.GetGasLimit() ?? gasLimitCalculator.GetGasLimit(parent, checked((long?)payloadAttributes.TargetGasLimit)), + payloadAttributes.GetGasLimit(parent, gasLimitCalculator), payloadAttributes.Timestamp, extraData ?? []) { diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs index c1a21b832830..3b0b9dc5a71a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; +using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -29,7 +30,7 @@ public byte[][]? Transactions } public bool NoTxPool { get; set; } public long GasLimit { get; set; } - public override long? GetGasLimit() => GasLimit; + public override long GetGasLimit(BlockHeader parent, IGasLimitCalculator gasLimitCalculator) => GasLimit; /// /// See diff --git a/src/Nethermind/Nethermind.Taiko/TaikoPayloadAttributes.cs b/src/Nethermind/Nethermind.Taiko/TaikoPayloadAttributes.cs index d7ef12156a66..708d5cc1c657 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoPayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoPayloadAttributes.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics.CodeAnalysis; +using Nethermind.Consensus; using Nethermind.Consensus.Producers; +using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Int256; @@ -14,7 +16,7 @@ public class TaikoPayloadAttributes : PayloadAttributes public BlockMetadata? BlockMetadata { get; set; } public L1Origin? L1Origin { get; set; } - public override long? GetGasLimit() => BlockMetadata!.GasLimit; + public override long GetGasLimit(BlockHeader parent, IGasLimitCalculator gasLimitCalculator) => BlockMetadata!.GasLimit; public override PayloadAttributesValidationResult Validate(ISpecProvider specProvider, int fcuVersion, [NotNullWhen(false)] out string? error) From 74e27297d75beb66cfccedb9888bf21d2317a1a0 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 14 May 2026 21:32:17 +0200 Subject: [PATCH 4/8] style(xdc): collapse XdcGasLimitCalculator.GetGasLimit to expression body --- .../Nethermind.Xdc/XdcGasLimitCalculator.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs index 559d2ffb52e3..e105fbc68860 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs @@ -12,13 +12,8 @@ namespace Nethermind.Xdc; internal class XdcGasLimitCalculator(ISpecProvider specProvider, IBlocksConfig blocksConfig) : IGasLimitCalculator { private readonly TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = new(specProvider, blocksConfig); - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) - { - IXdcReleaseSpec spec = specProvider.GetXdcSpec(parentHeader.Number + 1); - if (spec.IsDynamicGasLimitBlock) - { - return targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader, targetGasLimitOverride); - } - return blocksConfig.TargetBlockGasLimit ?? XdcConstants.DefaultTargetGasLimit; - } + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => + specProvider.GetXdcSpec(parentHeader.Number + 1).IsDynamicGasLimitBlock + ? targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader, targetGasLimitOverride) + : blocksConfig.TargetBlockGasLimit ?? XdcConstants.DefaultTargetGasLimit; } From 6abd4a38663b11118dd5ddfc837372963253e000 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Thu, 14 May 2026 21:44:01 +0200 Subject: [PATCH 5/8] refactor(consensus): rename targetGasLimitOverride to targetGasLimit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align with the spec field name (`PayloadAttributesV4.targetGasLimit`). The "override" suffix was redundant — a non-null value already means override behavior. Inside `TargetAdjustedGasLimitCalculator`, reuse the parameter via `??=` instead of an extra local. Also drop the unused `using Nethermind.Xdc.Spec` in XdcGasLimitCalculator (left over from the previous expression-body collapse) — was failing the `IDE0005` lint check. --- .../Contract/AuRaContractGasLimitOverrideTests.cs | 4 ++-- .../AuRaContractGasLimitOverride.cs | 6 +++--- .../Nethermind.Consensus.Test/ManualGasLimitCalculator.cs | 2 +- src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs | 2 +- src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs | 4 ++-- .../TargetAdjustedGasLimitCalculator.cs | 4 ++-- .../Nethermind.Optimism/OptimismGasLimitCalculator.cs | 2 +- src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs | 5 ++--- 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index 4cd134c33f46..8455d03b105d 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -77,7 +77,7 @@ public void Override_takes_precedence_over_contract_limit() (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inner) = BuildOverride(contractGasLimit: 42_000_000, innerResult: innerGasLimit); BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; - long result = calculator.GetGasLimit(parent, targetGasLimitOverride: overrideTarget); + long result = calculator.GetGasLimit(parent, targetGasLimit: overrideTarget); result.Should().Be(innerGasLimit); inner.Received(1).GetGasLimit(parent, overrideTarget); @@ -91,7 +91,7 @@ public void Override_is_forwarded_to_inner_when_no_contract_limit() (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inner) = BuildOverride(contractGasLimit: null, innerResult: innerGasLimit); BlockHeader parent = Build.A.BlockHeader.WithNumber(10).TestObject; - long result = calculator.GetGasLimit(parent, targetGasLimitOverride: overrideTarget); + long result = calculator.GetGasLimit(parent, targetGasLimit: overrideTarget); result.Should().Be(innerGasLimit); inner.Received(1).GetGasLimit(parent, overrideTarget); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index 56b1fec98385..fa1db515722e 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -39,9 +39,9 @@ private static UInt256 MinimalContractGasLimit // CL-supplied targetGasLimit (PayloadAttributesV4) takes precedence over the on-chain // contract value so validators can steer per-FCU; contract value is the fallback. - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => - targetGasLimitOverride is not null - ? _innerCalculator.GetGasLimit(parentHeader, targetGasLimitOverride) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) => + targetGasLimit is not null + ? _innerCalculator.GetGasLimit(parentHeader, targetGasLimit) : GetGasLimitFromContract(parentHeader) ?? _innerCalculator.GetGasLimit(parentHeader); private long? GetGasLimitFromContract(BlockHeader parentHeader) diff --git a/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs index 32d38435735d..0396e6f9a5a2 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/ManualGasLimitCalculator.cs @@ -8,6 +8,6 @@ namespace Nethermind.Consensus.Test public class ManualGasLimitCalculator : IGasLimitCalculator { public long GasLimit { get; set; } - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => GasLimit; + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) => GasLimit; } } diff --git a/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs b/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs index c014d7f1fbc4..fd03b31ba824 100644 --- a/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs +++ b/src/Nethermind/Nethermind.Consensus/FollowOtherMiners.cs @@ -10,7 +10,7 @@ public class FollowOtherMiners(ISpecProvider specProvider) : IGasLimitCalculator { private readonly ISpecProvider _specProvider = specProvider; - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) { long gasLimit = parentHeader.GasLimit; long newBlockNumber = parentHeader.Number + 1; diff --git a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs index 31fd8264ad15..ee6a775207b8 100644 --- a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs @@ -8,10 +8,10 @@ namespace Nethermind.Consensus public interface IGasLimitCalculator { /// - /// Computes the gas limit for the next block. When is supplied + /// Computes the gas limit for the next block. When is supplied /// (e.g. from PayloadAttributesV4.targetGasLimit introduced in Amsterdam), it takes precedence over /// any statically-configured target. Implementations that don't honor a per-call target may ignore it. /// - long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null); + long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null); } } diff --git a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs index f347d622ae7a..f4d9b2b3c4ae 100644 --- a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs @@ -13,12 +13,12 @@ public class TargetAdjustedGasLimitCalculator(ISpecProvider? specProvider, IBloc private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly IBlocksConfig _blocksConfig = blocksConfig ?? throw new ArgumentNullException(nameof(blocksConfig)); - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) { long parentGasLimit = parentHeader.GasLimit; long gasLimit = parentGasLimit; - long? targetGasLimit = targetGasLimitOverride ?? _blocksConfig.TargetBlockGasLimit; + targetGasLimit ??= _blocksConfig.TargetBlockGasLimit; long newBlockNumber = parentHeader.Number + 1; IReleaseSpec spec = _specProvider.GetSpec(newBlockNumber, parentHeader.Timestamp); // taking the parent timestamp is a temporary solution if (targetGasLimit is not null) diff --git a/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs b/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs index f6f1b696ada3..a88e343e6165 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismGasLimitCalculator.cs @@ -9,6 +9,6 @@ namespace Nethermind.Optimism; public class OptimismGasLimitCalculator : IGasLimitCalculator { - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) => throw new InvalidOperationException("GasLimit in Optimism should come from payload attributes."); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs index e105fbc68860..1199d32d9069 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs @@ -5,15 +5,14 @@ using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Xdc.Spec; namespace Nethermind.Xdc; internal class XdcGasLimitCalculator(ISpecProvider specProvider, IBlocksConfig blocksConfig) : IGasLimitCalculator { private readonly TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = new(specProvider, blocksConfig); - public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimitOverride = null) => + public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) => specProvider.GetXdcSpec(parentHeader.Number + 1).IsDynamicGasLimitBlock - ? targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader, targetGasLimitOverride) + ? targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader, targetGasLimit) : blocksConfig.TargetBlockGasLimit ?? XdcConstants.DefaultTargetGasLimit; } From fcd8b670a429e1d9632d2194f4d8a7886b9b4eca Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Fri, 15 May 2026 07:11:04 +0200 Subject: [PATCH 6/8] fix(aura): relax IsGasLimitValid for CL-supplied targetGasLimit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AuRaContractGasLimitOverride.GetGasLimit` prefers the CL-supplied `targetGasLimit` (PayloadAttributesV4) over the on-chain contract value, but `IsGasLimitValid` — called from `AuRaBlockProcessor` — still strictly required the block to match the contract. On an AuRa chain with an active gas-limit contract that ever activates EIP-7843, a producer using the override path would emit a block its own validator rejects. Accept any block gas limit within parent ± delta when it differs from the contract value (HeaderValidator independently enforces the full consensus rule). Inject `ISpecProvider` so the bound divisor is read from the active release spec instead of being hardcoded. --- .../AuRaContractGasLimitOverrideTests.cs | 1 + .../AuRaContractGasLimitOverrideTests.cs | 27 ++++++++++++++++++- .../AuRaContractGasLimitOverride.cs | 18 +++++++++++-- .../AuRaGasLimitOverrideFactory.cs | 1 + 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs index 9f9fee3620b2..ad6fb0db6eb2 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs @@ -48,6 +48,7 @@ public void GetGasLimit(bool minimum2MlnGasPerBlockWhenUsingBlockGasLimit, long new List { blockGasLimitContract1, blockGasLimitContract2, blockGasLimitContract3 }, new AuRaContractGasLimitOverride.Cache(), minimum2MlnGasPerBlockWhenUsingBlockGasLimit, + MainnetSpecProvider.Instance, new TargetAdjustedGasLimitCalculator(MainnetSpecProvider.Instance, config), LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index 8455d03b105d..d6b8dd828cc8 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -18,7 +18,9 @@ using Nethermind.Core.Test.Builders; using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; +using Nethermind.Specs.Forks; using NSubstitute; using NUnit.Framework; @@ -83,6 +85,28 @@ public void Override_takes_precedence_over_contract_limit() inner.Received(1).GetGasLimit(parent, overrideTarget); } + // Mirrors the GetGasLimit precedence: a producer using a CL targetGasLimit may emit a + // block whose gas limit differs from the contract value but still respects parent ± delta. + // IsGasLimitValid must accept such blocks; otherwise validation would reject what the + // producer legitimately built. + [Test] + public void IsGasLimitValid_accepts_within_parent_delta_when_contract_differs() + { + const long parentGasLimit = 30_000_000; + const long contractGasLimit = 42_000_000; + (AuRaContractGasLimitOverride calculator, _) = BuildOverride(contractGasLimit, innerResult: 0); + BlockHeader parent = Build.A.BlockHeader.WithNumber(10).WithGasLimit(parentGasLimit).TestObject; + + // parent / 1024 ~= 29_296 — within range + bool inRange = calculator.IsGasLimitValid(parent, parentGasLimit + 29_000, out long? expected); + // way beyond delta — outside range + bool outOfRange = calculator.IsGasLimitValid(parent, parentGasLimit + 10_000_000, out _); + + inRange.Should().BeTrue(); + expected.Should().Be(contractGasLimit); + outOfRange.Should().BeFalse(); + } + [Test] public void Override_is_forwarded_to_inner_when_no_contract_limit() { @@ -107,6 +131,7 @@ private static (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inn contracts, new AuRaContractGasLimitOverride.Cache(), minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, + new TestSpecProvider(Prague.Instance), inner, LimboLogs.Instance); return (calculator, inner); @@ -147,7 +172,7 @@ IReadOnlyTxProcessingEnvFactory txProcessingEnvFactory blockGasLimitContractTransition.Key, txProcessingEnvFactory.Create()); - return new AuRaContractGasLimitOverride(new[] { gasLimitContract }, gasLimitOverrideCache, false, new FollowOtherMiners(specProvider), LimboLogs.Instance); + return new AuRaContractGasLimitOverride(new[] { gasLimitContract }, gasLimitOverrideCache, false, specProvider, new FollowOtherMiners(specProvider), LimboLogs.Instance); } protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, IConfigProvider configProvider) => diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index fa1db515722e..17e21bea7c7a 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -9,6 +9,7 @@ using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Logging; @@ -16,8 +17,9 @@ namespace Nethermind.Consensus.AuRa { public class AuRaContractGasLimitOverride( IList contracts, -AuRaContractGasLimitOverride.Cache cache, + AuRaContractGasLimitOverride.Cache cache, bool minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract, + ISpecProvider specProvider, IGasLimitCalculator innerCalculator, ILogManager logManager) : IGasLimitCalculator { @@ -34,6 +36,7 @@ private static UInt256 MinimalContractGasLimit private readonly IList _contracts = contracts ?? throw new ArgumentNullException(nameof(contracts)); private readonly Cache _cache = cache ?? throw new ArgumentNullException(nameof(cache)); private readonly bool _minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract = minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract; + private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly IGasLimitCalculator _innerCalculator = innerCalculator ?? throw new ArgumentNullException(nameof(innerCalculator)); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); @@ -100,7 +103,18 @@ public class Cache public bool IsGasLimitValid(BlockHeader parentHeader, in long gasLimit, out long? expectedGasLimit) { expectedGasLimit = GetGasLimitFromContract(parentHeader); - return expectedGasLimit is null || expectedGasLimit == gasLimit; + if (expectedGasLimit is null || expectedGasLimit == gasLimit) + { + return true; + } + + // CL-supplied targetGasLimit (PayloadAttributesV4) lets producers deviate from the + // on-chain contract value (see GetGasLimit precedence above). Accept any block gas + // limit that respects the standard parent ± delta bound — wider consensus rules are + // enforced separately by HeaderValidator. + IReleaseSpec spec = _specProvider.GetSpec(parentHeader.Number + 1, parentHeader.Timestamp); + long maxDelta = parentHeader.GasLimit / spec.GasLimitBoundDivisor; + return Math.Abs(gasLimit - parentHeader.GasLimit) <= maxDelta; } } } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs index ee52228e56df..3bfc287ffbae 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs @@ -40,6 +40,7 @@ public class AuRaGasLimitOverrideFactory( .ToArray(), gasLimitOverrideCache, auraConfig.Minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract, + specProvider, new TargetAdjustedGasLimitCalculator(specProvider, blocksConfig), logManager); From e60c92c906e45d761fdb21ababbf6410ab3b9071 Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Fri, 15 May 2026 08:05:21 +0200 Subject: [PATCH 7/8] refactor(aura): reuse inner calculator for IsGasLimitValid bound check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AuRaContractGasLimitOverride.IsGasLimitValid` now round-trips the candidate gas limit through `_innerCalculator` (a `TargetAdjustedGasLimitCalculator` in production): if the calculator returns the candidate unchanged it's within the standard parent ± delta range, otherwise it's been clamped. Drops the duplicated delta math and the `ISpecProvider` dependency added in the previous commit. Also strip the verbose XML doc comments added on `TargetGasLimit`, `PayloadAttributes.GetGasLimit`, `IGasLimitCalculator.GetGasLimit`, and the explanatory test comments. --- .../AuRaContractGasLimitOverrideTests.cs | 1 - .../AuRaContractGasLimitOverrideTests.cs | 16 +++++++-------- .../AuRaContractGasLimitOverride.cs | 20 +++---------------- .../AuRaGasLimitOverrideFactory.cs | 1 - .../TargetAdjustedGasLimitCalculatorTests.cs | 2 -- .../IGasLimitCalculator.cs | 5 ----- .../Producers/PayloadAttributes.cs | 12 ----------- 7 files changed, 10 insertions(+), 47 deletions(-) diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs index ad6fb0db6eb2..9f9fee3620b2 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaContractGasLimitOverrideTests.cs @@ -48,7 +48,6 @@ public void GetGasLimit(bool minimum2MlnGasPerBlockWhenUsingBlockGasLimit, long new List { blockGasLimitContract1, blockGasLimitContract2, blockGasLimitContract3 }, new AuRaContractGasLimitOverride.Cache(), minimum2MlnGasPerBlockWhenUsingBlockGasLimit, - MainnetSpecProvider.Instance, new TargetAdjustedGasLimitCalculator(MainnetSpecProvider.Instance, config), LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index d6b8dd828cc8..7d352f1b0253 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -85,21 +85,20 @@ public void Override_takes_precedence_over_contract_limit() inner.Received(1).GetGasLimit(parent, overrideTarget); } - // Mirrors the GetGasLimit precedence: a producer using a CL targetGasLimit may emit a - // block whose gas limit differs from the contract value but still respects parent ± delta. - // IsGasLimitValid must accept such blocks; otherwise validation would reject what the - // producer legitimately built. [Test] public void IsGasLimitValid_accepts_within_parent_delta_when_contract_differs() { const long parentGasLimit = 30_000_000; const long contractGasLimit = 42_000_000; - (AuRaContractGasLimitOverride calculator, _) = BuildOverride(contractGasLimit, innerResult: 0); + AuRaContractGasLimitOverride calculator = new( + [StubContract(contractGasLimit)], + new AuRaContractGasLimitOverride.Cache(), + minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, + new TargetAdjustedGasLimitCalculator(new TestSpecProvider(Prague.Instance), new BlocksConfig()), + LimboLogs.Instance); BlockHeader parent = Build.A.BlockHeader.WithNumber(10).WithGasLimit(parentGasLimit).TestObject; - // parent / 1024 ~= 29_296 — within range bool inRange = calculator.IsGasLimitValid(parent, parentGasLimit + 29_000, out long? expected); - // way beyond delta — outside range bool outOfRange = calculator.IsGasLimitValid(parent, parentGasLimit + 10_000_000, out _); inRange.Should().BeTrue(); @@ -131,7 +130,6 @@ private static (AuRaContractGasLimitOverride calculator, IGasLimitCalculator inn contracts, new AuRaContractGasLimitOverride.Cache(), minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract: false, - new TestSpecProvider(Prague.Instance), inner, LimboLogs.Instance); return (calculator, inner); @@ -172,7 +170,7 @@ IReadOnlyTxProcessingEnvFactory txProcessingEnvFactory blockGasLimitContractTransition.Key, txProcessingEnvFactory.Create()); - return new AuRaContractGasLimitOverride(new[] { gasLimitContract }, gasLimitOverrideCache, false, specProvider, new FollowOtherMiners(specProvider), LimboLogs.Instance); + return new AuRaContractGasLimitOverride(new[] { gasLimitContract }, gasLimitOverrideCache, false, new FollowOtherMiners(specProvider), LimboLogs.Instance); } protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, IConfigProvider configProvider) => diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs index 17e21bea7c7a..cce86a5b3b7e 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaContractGasLimitOverride.cs @@ -9,7 +9,6 @@ using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Logging; @@ -19,7 +18,6 @@ public class AuRaContractGasLimitOverride( IList contracts, AuRaContractGasLimitOverride.Cache cache, bool minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract, - ISpecProvider specProvider, IGasLimitCalculator innerCalculator, ILogManager logManager) : IGasLimitCalculator { @@ -36,12 +34,9 @@ private static UInt256 MinimalContractGasLimit private readonly IList _contracts = contracts ?? throw new ArgumentNullException(nameof(contracts)); private readonly Cache _cache = cache ?? throw new ArgumentNullException(nameof(cache)); private readonly bool _minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract = minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract; - private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly IGasLimitCalculator _innerCalculator = innerCalculator ?? throw new ArgumentNullException(nameof(innerCalculator)); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - // CL-supplied targetGasLimit (PayloadAttributesV4) takes precedence over the on-chain - // contract value so validators can steer per-FCU; contract value is the fallback. public long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null) => targetGasLimit is not null ? _innerCalculator.GetGasLimit(parentHeader, targetGasLimit) @@ -103,18 +98,9 @@ public class Cache public bool IsGasLimitValid(BlockHeader parentHeader, in long gasLimit, out long? expectedGasLimit) { expectedGasLimit = GetGasLimitFromContract(parentHeader); - if (expectedGasLimit is null || expectedGasLimit == gasLimit) - { - return true; - } - - // CL-supplied targetGasLimit (PayloadAttributesV4) lets producers deviate from the - // on-chain contract value (see GetGasLimit precedence above). Accept any block gas - // limit that respects the standard parent ± delta bound — wider consensus rules are - // enforced separately by HeaderValidator. - IReleaseSpec spec = _specProvider.GetSpec(parentHeader.Number + 1, parentHeader.Timestamp); - long maxDelta = parentHeader.GasLimit / spec.GasLimitBoundDivisor; - return Math.Abs(gasLimit - parentHeader.GasLimit) <= maxDelta; + return expectedGasLimit is null + || expectedGasLimit == gasLimit + || _innerCalculator.GetGasLimit(parentHeader, gasLimit) == gasLimit; } } } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs index 3bfc287ffbae..ee52228e56df 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaGasLimitOverrideFactory.cs @@ -40,7 +40,6 @@ public class AuRaGasLimitOverrideFactory( .ToArray(), gasLimitOverrideCache, auraConfig.Minimum2MlnGasPerBlockWhenUsingBlockGasLimitContract, - specProvider, new TargetAdjustedGasLimitCalculator(specProvider, blocksConfig), logManager); diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 2667a17eeeb7..32f1d3569208 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs @@ -48,9 +48,7 @@ public void Is_bump_on_1559_eip_block() public void Is_calculating_correct_gasLimit(long parentGasLimit, long configTarget, long expected) => Calc(Prague.Instance, parentGasLimit, configTarget: configTarget).Should().Be(expected); - // Config target would decrease, override pushes up — confirms override drives the direction. [TestCase(30_000_000, 20_000_000, 50_000_000, 30029295)] - // Config target would increase, override pulls down — confirms override drives the direction. [TestCase(30_000_000, 100_000_000, 20_000_000, 29970705)] public void Override_takes_precedence_over_config_target(long parentGasLimit, long configTarget, long overrideTarget, long expected) => Calc(Prague.Instance, parentGasLimit, configTarget, overrideTarget).Should().Be(expected); diff --git a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs index ee6a775207b8..51d075620fb0 100644 --- a/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/IGasLimitCalculator.cs @@ -7,11 +7,6 @@ namespace Nethermind.Consensus { public interface IGasLimitCalculator { - /// - /// Computes the gas limit for the next block. When is supplied - /// (e.g. from PayloadAttributesV4.targetGasLimit introduced in Amsterdam), it takes precedence over - /// any statically-configured target. Implementations that don't honor a per-call target may ignore it. - /// long GetGasLimit(BlockHeader parentHeader, long? targetGasLimit = null); } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index 44ef4225684f..1b3b56f4fe2a 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -28,20 +28,8 @@ public class PayloadAttributes public ulong? SlotNumber { get; set; } - /// - /// Target gas limit the CL wants the EL to aim for when building a local payload, as defined by - /// PayloadAttributesV4 (Amsterdam). The EL applies the standard gas-limit adjustment relative to - /// the parent block, so the resulting may differ from this - /// target. Replaces the static EL-side target configuration for V4+ FCUs. - /// public long? TargetGasLimit { get; set; } - /// - /// Resolves the gas limit for the block being built from these attributes. The default - /// implementation routes through - /// so the standard parent ± delta adjustment is applied. Subclasses that supply an unadjusted - /// value directly (e.g. Boost, Optimism, Taiko) override this. - /// public virtual long GetGasLimit(BlockHeader parent, IGasLimitCalculator gasLimitCalculator) => gasLimitCalculator.GetGasLimit(parent, TargetGasLimit); From 8a82ef539c50930fac3da54be0bb96a8165ac8e0 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Mon, 18 May 2026 15:47:00 +0200 Subject: [PATCH 8/8] feat(engine/ssz): add target_gas_limit to PayloadAttributesV4 wire (#11647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plumbs the new TargetGasLimit field added in this PR through the SSZ-REST wire schema for engine_forkchoiceUpdatedV4 (PR #764). Without this, CLs that include target_gas_limit in the SSZ body get rejected as "Malformed SSZ body" because the 8 extra trailing bytes shift the inner withdrawals offset. The wire field is a required fixed-size uint64 (per execution-apis ssz-encoding.md), so a missing value fails SSZ decode directly — the V4 null-guard in PayloadAttributes.GetInvalidPayloadAttributesMessage remains as defense-in-depth for the JSON path. Changes: - PayloadAttributesWire gains target_gas_limit at the end of the container (matches execution-apis ssz-encoding.md PayloadAttributesV4). - SszCodec.PayloadAttributesFromWire forwards the wire ulong as long to PayloadAttributes.TargetGasLimit. - DecodeFcuV4Request roundtrip test now also asserts target_gas_limit survives encode/decode, using a diverse-byte value (0x0123456789ABCDEF). --- .../SszRest/SszCodecTests.cs | 4 ++++ .../Nethermind.Merge.Plugin/SszRest/SszCodec.cs | 9 ++++++--- .../Nethermind.Merge.Plugin/SszRest/SszWireTypes.cs | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/SszRest/SszCodecTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/SszRest/SszCodecTests.cs index 73dafbfdfbeb..2a902d754248 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/SszRest/SszCodecTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/SszRest/SszCodecTests.cs @@ -533,6 +533,7 @@ public void DecodeFcuV3Request_spec_layout_roundtrips_parent_beacon_block_root() public void DecodeFcuV4Request_spec_layout_roundtrips_parent_beacon_block_root_and_slot_number() { ulong expectedSlot = 0xAABBCCDD_11223344UL; + ulong expectedTargetGasLimit = 0x0123456789ABCDEFUL; ForkchoiceUpdatedRequestWire wire = new() { @@ -552,6 +553,7 @@ public void DecodeFcuV4Request_spec_layout_roundtrips_parent_beacon_block_root_a Withdrawals = [], ParentBeaconBlockRoot = TestItem.KeccakE, SlotNumber = expectedSlot, + TargetGasLimit = expectedTargetGasLimit, } ] }; @@ -569,6 +571,8 @@ public void DecodeFcuV4Request_spec_layout_roundtrips_parent_beacon_block_root_a "parent_beacon_block_root must round-trip in V4 as a fixed Bytes32"); attrs.SlotNumber.Should().Be(expectedSlot, "slot_number must be decoded from the fixed uint64 that follows parent_beacon_block_root"); + attrs.TargetGasLimit.Should().Be((long)expectedTargetGasLimit, + "target_gas_limit must be decoded from the fixed uint64 that follows slot_number"); attrs.SuggestedFeeRecipient.Should().Be(TestItem.AddressB); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszCodec.cs b/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszCodec.cs index 375852e8531e..7ea4640ff8f5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszCodec.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszCodec.cs @@ -288,7 +288,8 @@ internal static PayloadAttributes PayloadAttributesFromWire(PayloadAttributesWir BuildPayloadAttributes(pa.Timestamp, pa.PrevRandao, pa.SuggestedFeeRecipient, withdrawals: pa.Withdrawals.ToDomain(), parentBeaconBlockRoot: pa.ParentBeaconBlockRoot, - slotNumber: pa.SlotNumber); + slotNumber: pa.SlotNumber, + targetGasLimit: (long)pa.TargetGasLimit); private static PayloadAttributes BuildPayloadAttributes( ulong timestamp, @@ -296,14 +297,16 @@ private static PayloadAttributes BuildPayloadAttributes( Address suggestedFeeRecipient, Withdrawal[]? withdrawals = null, Hash256? parentBeaconBlockRoot = null, - ulong? slotNumber = null) => new() + ulong? slotNumber = null, + long? targetGasLimit = null) => new() { Timestamp = timestamp, PrevRandao = prevRandao, SuggestedFeeRecipient = suggestedFeeRecipient, Withdrawals = withdrawals, ParentBeaconBlockRoot = parentBeaconBlockRoot, - SlotNumber = slotNumber + SlotNumber = slotNumber, + TargetGasLimit = targetGasLimit }; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszWireTypes.cs b/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszWireTypes.cs index 6388a47e7149..66a90bd82f8c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszWireTypes.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/SszRest/SszWireTypes.cs @@ -80,6 +80,7 @@ public partial struct PayloadAttributesWire [SszList(16)] public SszWithdrawal[]? Withdrawals { get; set; } public Hash256 ParentBeaconBlockRoot { get; set; } public ulong SlotNumber { get; set; } + public ulong TargetGasLimit { get; set; } } [SszContainer]