diff --git a/src/Nethermind/Nethermind.Consensus.Test/PayloadAttributesValidateTests.cs b/src/Nethermind/Nethermind.Consensus.Test/PayloadAttributesValidateTests.cs index 3e543f9c7647..53e056266516 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/PayloadAttributesValidateTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/PayloadAttributesValidateTests.cs @@ -42,6 +42,8 @@ private static ISpecProvider MakeSpecProvider(bool isAmsterdam) new object[] { /* isAmsterdam */ true, /* withSlot */ true, /* fcu */ PayloadAttributesVersions.V4, PayloadAttributesValidationResult.Success, null!, null! }, new object[] { /* isAmsterdam */ false, /* withSlot */ true, /* fcu */ PayloadAttributesVersions.V3, + PayloadAttributesValidationResult.InvalidPayloadAttributes, null!, null! }, + new object[] { /* isAmsterdam */ true, /* withSlot */ false, /* fcu */ PayloadAttributesVersions.V3, PayloadAttributesValidationResult.UnsupportedFork, null!, null! }, ]; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index bf89f9d79ab6..876a88830f85 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -163,9 +163,11 @@ private static PayloadAttributesValidationResult ValidateVersion( if (actualVersion != timestampVersion) { error = $"{methodName}{timestampVersion} expected"; - // FCU also doesn't support this fork → UnsupportedFork (post-Paris only) - bool unsupportedFork = timestampVersion >= PayloadAttributesVersions.V2 && - (actualVersion > timestampVersion || fcuVersion != timestampVersion); + bool unsupportedFork = timestampVersion >= PayloadAttributesVersions.V2 && ( + // attrs from a newer fork AND FCU version is also too new for this timestamp + actualVersion > timestampVersion ? fcuVersion > timestampVersion + // attrs from an older fork AND the FCU version doesn't match either + : fcuVersion != timestampVersion); return unsupportedFork ? PayloadAttributesValidationResult.UnsupportedFork : PayloadAttributesValidationResult.InvalidPayloadAttributes; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index cb088e136db2..57ad7b473e46 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -856,7 +856,7 @@ public static IEnumerable ForkchoiceUpdatedV3DeclinedTestCaseSourc yield return new TestCaseData(Shanghai.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV2), true) { TestName = "ForkchoiceUpdatedV2 To Request Shanghai Payload, Zero Beacon Root", - ExpectedResult = MergeErrorCodes.UnsupportedFork, + ExpectedResult = MergeErrorCodes.InvalidPayloadAttributes, }; yield return new TestCaseData(Cancun.Instance, nameof(IEngineRpcModule.engine_forkchoiceUpdatedV2), true) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs index e5adcfc64343..2e1f94152a09 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs @@ -88,6 +88,12 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin return result; } + if (blobVersionedHashes is null) + { + error = "Blob versioned hashes must not be null"; + return ValidationResult.Fail; + } + Result transactionDecodingResult = executionPayload.TryGetTransactions(); if (transactionDecodingResult.IsError) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs index d73d40dced8e..9edb62cfca5f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -130,7 +130,7 @@ public async Task> HandleAsync(ExecutionPayload r if (!HeaderValidator.ValidateHash(block!.Header, out Hash256 actualHash)) { if (_logger.IsWarn) _logger.Warn(InvalidBlockHelper.GetMessage(block, "invalid block hash")); - RecordBadBlock(block); + Nethermind.Blockchain.Metrics.BadBlocks++; return NewPayloadV1Result.Invalid(null, $"Invalid block hash {request.BlockHash} does not match calculated hash {actualHash}."); } diff --git a/src/Nethermind/Nethermind.Network/NodeFilter.cs b/src/Nethermind/Nethermind.Network/NodeFilter.cs index f9d69c1ef0d3..973f1e8568a3 100644 --- a/src/Nethermind/Nethermind.Network/NodeFilter.cs +++ b/src/Nethermind/Nethermind.Network/NodeFilter.cs @@ -105,6 +105,13 @@ public void Touch(IPAddress ipAddress, bool exactOnly = false) _cache.Set(GetKey(ipAddress, exactOnly), Environment.TickCount64); } + /// + /// Removes a previously recorded address so that the next call for the + /// same address succeeds. Used to permit reconnection after a session ends. + /// + public void Remove(IPAddress ipAddress, bool exactOnly = false) + => _cache?.Delete(GetKey(ipAddress, exactOnly)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private IpSubnetKey GetKey(IPAddress ipAddress, bool exactOnly) => _exactMatchOnly || exactOnly diff --git a/src/Nethermind/Nethermind.Network/PeerManager.cs b/src/Nethermind/Nethermind.Network/PeerManager.cs index 7c405d90563a..2b7bc20cc0f0 100644 --- a/src/Nethermind/Nethermind.Network/PeerManager.cs +++ b/src/Nethermind/Nethermind.Network/PeerManager.cs @@ -925,8 +925,14 @@ void TraceHardLimitDisconnect() => _logger.Trace($"Initiating disconnect with {session} {DisconnectReason.HardLimitTooManyPeers} {DisconnectType.Local}"); } + /// + /// Static and bootnode peers bypass the IP filter so that a single failed attempt does not block reconnection + /// for the full filter timeout window. Their retry cadence is governed by + /// and the outgoing rate-limiter instead. + /// private bool ShouldContactPeer(Peer peer) - => _rlpxHost.ShouldContact(peer.Node.Address.Address, exactOnly: peer.Node.IsStatic || peer.Node.IsBootnode); + => peer.Node.IsStatic || peer.Node.IsBootnode + || _rlpxHost.ShouldContact(peer.Node.Address.Address); /// /// Fast-path guard for the peer-added event: checks throttle before the IP filter diff --git a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs index 5e7174989e73..08069348a4be 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs @@ -501,6 +501,15 @@ private void OnSessionDisconnected(ISession session, DisconnectEventArgs args) return; } + // Release the peer's IP from the filter so it can reconnect immediately. + // For inbound sessions the filter entry was created by ShouldRejectInbound; clearing it here + // prevents the 5-minute window from blocking legitimate reconnection after a dropped session. + if (session.Node?.Address?.Address is { } remoteIp) + { + bool exactOnly = session.Node.IsStatic || session.Node.IsBootnode; + _nodeFilter.Remove(remoteIp, exactOnly); + } + subscription.DetachSession(); _sessionMonitor.RemoveSession(session); try