From 97b1bd464158be349cac1a175b154f201168ca8a Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 23 Mar 2026 13:37:29 +0300 Subject: [PATCH 1/3] Dispose more --- .../Nethermind.Core.Test/RlpTests.cs | 15 +++++++ .../Eth/V62/Messages/BlockBodiesMessage.cs | 6 +++ .../Messages/BlockBodiesMessageSerializer.cs | 18 ++++++-- .../Snap/Messages/AccountRangeMessage.cs | 4 +- .../Messages/AccountRangeMessageSerializer.cs | 44 ++++++++++++------- .../Messages/ByteCodesMessageSerializer.cs | 23 +++++++--- .../Messages/GetTrieNodesMessageSerializer.cs | 35 ++++++++++----- .../StorageRangesMessageSerializer.cs | 36 +++++++++------ .../Messages/TrieNodesMessageSerializer.cs | 23 +++++++--- .../NettyRlpStream.cs | 20 +++++++-- 10 files changed, 163 insertions(+), 61 deletions(-) diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index d0d1e912160c..2ee125eb130c 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -569,5 +569,20 @@ private static byte[] BuildLongFormRlp(int prefix, int contentLength) return data; } + private sealed class DisposableTracker(Action onDispose) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + onDispose(); + } + } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs index 515765ec3c25..b650109c7859 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs @@ -34,6 +34,12 @@ public BlockBodiesMessage(BlockBody?[] bodies) Bodies = new OwnedBlockBodies(bodies); } + public override void Dispose() + { + base.Dispose(); + Bodies?.Dispose(); + } + public override string ToString() => $"{nameof(BlockBodiesMessage)}({Bodies?.Bodies?.Length ?? 0})"; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs index 58b7c399e179..e667c9f70ccf 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs @@ -51,14 +51,24 @@ public int GetLength(BlockBodiesMessage message, out int contentLength) public BlockBodiesMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startingPosition = ctx.Position; - BlockBody[]? bodies = ctx.DecodeArray(_blockBodyDecoder, false, limit: RlpLimit); - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startingPosition)); + try + { + BlockBody[]? bodies = ctx.DecodeArray(_blockBodyDecoder, false, limit: RlpLimit); + OwnedBlockBodies ownedBodies = new(bodies, memoryOwner); + memoryOwner = null; + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startingPosition)); - return new() { Bodies = new(bodies, memoryOwner) }; + return new() { Bodies = ownedBodies }; + } + catch + { + memoryOwner?.Dispose(); + throw; + } } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs index 365001580965..2437bdc197b2 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs @@ -23,8 +23,8 @@ public class AccountRangeMessage : SnapMessageBase public override void Dispose() { base.Dispose(); - PathsWithAccounts.Dispose(); - Proofs.Dispose(); + PathsWithAccounts?.Dispose(); + Proofs?.Dispose(); } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs index e7889eb9ecfa..a91495c25f78 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs @@ -50,31 +50,43 @@ public void Serialize(IByteBuffer byteBuffer, AccountRangeMessage message) public AccountRangeMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; - - ctx.ReadSequenceLength(); - AccountRangeMessage message = new(); - message.RequestId = ctx.DecodeLong(); + ArrayPoolList? pathsWithAccounts = null; - int pwasCheck = ctx.ReadSequenceLength() + ctx.Position; - int count = ctx.PeekNumberOfItemsRemaining(pwasCheck); - ctx.GuardLimit(count, SnapMessageLimits.AccountRangeEntriesRlpLimit); - ArrayPoolList pathsWithAccounts = new(count); - for (int i = 0; i < count; i++) + try { ctx.ReadSequenceLength(); - pathsWithAccounts.Add(new PathWithAccount(ctx.DecodeKeccak(), _decoder.Decode(ref ctx))); - } + message.RequestId = ctx.DecodeLong(); - message.PathsWithAccounts = pathsWithAccounts; - message.Proofs = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + int pwasCheck = ctx.ReadSequenceLength() + ctx.Position; + int count = ctx.PeekNumberOfItemsRemaining(pwasCheck); + ctx.GuardLimit(count, SnapMessageLimits.AccountRangeEntriesRlpLimit); + pathsWithAccounts = new ArrayPoolList(count); + for (int i = 0; i < count; i++) + { + ctx.ReadSequenceLength(); + pathsWithAccounts.Add(new PathWithAccount(ctx.DecodeKeccak(), _decoder.Decode(ref ctx))); + } - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + message.PathsWithAccounts = pathsWithAccounts; + pathsWithAccounts = null; + message.Proofs = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; - return message; + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + + return message; + } + catch + { + pathsWithAccounts?.Dispose(); + message.Dispose(); + memoryOwner?.Dispose(); + throw; + } } private (int contentLength, int pwasLength) GetLength(AccountRangeMessage message) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs index 6ef2fbe7ff0c..6b64ac8fca14 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs @@ -23,17 +23,28 @@ public void Serialize(IByteBuffer byteBuffer, ByteCodesMessage message) public ByteCodesMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; + RlpByteArrayList? list = null; - ctx.ReadSequenceLength(); - long requestId = ctx.DecodeLong(); + try + { + ctx.ReadSequenceLength(); + long requestId = ctx.DecodeLong(); - RlpByteArrayList list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); - return new ByteCodesMessage(list) { RequestId = requestId }; + return new ByteCodesMessage(list) { RequestId = requestId }; + } + catch + { + list?.Dispose(); + memoryOwner?.Dispose(); + throw; + } } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs index 6b9e232eac3e..c5433cf3608c 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs @@ -111,22 +111,37 @@ private static void EncodePaths(RlpStream stream, IReadOnlyList paths public GetTrieNodesMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startingPosition = ctx.Position; + GetTrieNodesMessage message = new(); + IRlpItemList? rawPaths = null; - ctx.ReadSequenceLength(); - long requestId = ctx.DecodeLong(); - Hash256? rootHash = ctx.DecodeKeccak(); + try + { + ctx.ReadSequenceLength(); + message.RequestId = ctx.DecodeLong(); + Hash256? rootHash = ctx.DecodeKeccak(); + message.RootHash = rootHash; - IRlpItemList rawPaths = RlpItemList.DecodeList(ref ctx, memoryOwner); - ValidatePathGroups(rawPaths); - RlpPathGroupList paths = new(rawPaths); + rawPaths = RlpItemList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; + ValidatePathGroups(rawPaths); + message.Paths = new RlpPathGroupList(rawPaths); + rawPaths = null; - long bytes = ctx.DecodeLong(); - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startingPosition)); + message.Bytes = ctx.DecodeLong(); + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startingPosition)); - return new GetTrieNodesMessage { RequestId = requestId, RootHash = rootHash, Paths = paths, Bytes = bytes }; + return message; + } + catch + { + rawPaths?.Dispose(); + message.Dispose(); + memoryOwner?.Dispose(); + throw; + } } private static void ValidatePathGroups(IRlpItemList paths) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs index f3c641724391..13015fe86e5a 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs @@ -59,28 +59,38 @@ public void Serialize(IByteBuffer byteBuffer, StorageRangeMessage message) public StorageRangeMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; ctx.ReadSequenceLength(); StorageRangeMessage message = new(); - message.RequestId = ctx.DecodeLong(); + try + { + message.RequestId = ctx.DecodeLong(); - message.Slots = ctx.DecodeArrayPoolList>(static (ref Rlp.ValueDecoderContext outerCtx) => - outerCtx.DecodeArrayPoolList(static (ref Rlp.ValueDecoderContext innerCtx) => - { - innerCtx.ReadSequenceLength(); - Hash256 path = innerCtx.DecodeKeccak(); - byte[] value = innerCtx.DecodeByteArray(StorageSlotValueRlpLimit); - return new PathWithStorageSlot(in path.ValueHash256, value); - }, limit: SnapMessageLimits.StorageRangeSlotsPerAccountRlpLimit), limit: SnapMessageLimits.StorageRangeAccountsRlpLimit); - message.Proofs = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + message.Slots = ctx.DecodeArrayPoolList>(static (ref Rlp.ValueDecoderContext outerCtx) => + outerCtx.DecodeArrayPoolList(static (ref Rlp.ValueDecoderContext innerCtx) => + { + innerCtx.ReadSequenceLength(); + Hash256 path = innerCtx.DecodeKeccak(); + byte[] value = innerCtx.DecodeByteArray(StorageSlotValueRlpLimit); + return new PathWithStorageSlot(in path.ValueHash256, value); + }, limit: SnapMessageLimits.StorageRangeSlotsPerAccountRlpLimit), limit: SnapMessageLimits.StorageRangeAccountsRlpLimit); + message.Proofs = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); - return message; + return message; + } + catch + { + message.Dispose(); + memoryOwner?.Dispose(); + throw; + } } private static (int contentLength, int allSlotsLength, int[] accountSlotsLengths) CalculateLengths(StorageRangeMessage message) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs index 4c4055ff1666..00ec7b5a3aa3 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs @@ -23,17 +23,28 @@ public void Serialize(IByteBuffer byteBuffer, TrieNodesMessage message) public TrieNodesMessage Deserialize(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; + RlpByteArrayList? list = null; - ctx.ReadSequenceLength(); - long requestId = ctx.DecodeLong(); + try + { + ctx.ReadSequenceLength(); + long requestId = ctx.DecodeLong(); - RlpByteArrayList list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); - return new TrieNodesMessage(list) { RequestId = requestId }; + return new TrieNodesMessage(list) { RequestId = requestId }; + } + catch + { + list?.Dispose(); + memoryOwner?.Dispose(); + throw; + } } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/NettyRlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/NettyRlpStream.cs index 992233495a77..191cd23fd0f6 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/NettyRlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/NettyRlpStream.cs @@ -99,12 +99,24 @@ public static void WriteByteArrayList(IByteBuffer byteBuffer, IByteArrayList lis public static RlpByteArrayList DecodeByteArrayList(IByteBuffer byteBuffer) { - NettyBufferMemoryOwner memoryOwner = new(byteBuffer); + NettyBufferMemoryOwner? memoryOwner = new(byteBuffer); Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; - RlpByteArrayList list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); - byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); - return list; + RlpByteArrayList? list = null; + + try + { + list = RlpByteArrayList.DecodeList(ref ctx, memoryOwner); + memoryOwner = null; + byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startPos)); + return list; + } + catch + { + list?.Dispose(); + memoryOwner?.Dispose(); + throw; + } } } } From 99afa17a0db139b77c0c360d7b4ea0d374485653 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 23 Mar 2026 14:30:28 +0300 Subject: [PATCH 2/3] Dispose in rare case --- .../Nethermind.Serialization.Rlp/Rlp.cs | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 8d0df9e7b67e..8363691c9348 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -1463,25 +1463,53 @@ public ArrayPoolList DecodeArrayPoolList(DecodeRlpValue decodeItem, boo int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : null); GuardLimit(count, limit); ArrayPoolList result = new(count, count); - for (int i = 0; i < result.Count; i++) + int i = 0; + try { - if (PeekByte() == OfEmptyList[0]) + for (; i < result.Count; i++) { - result[i] = defaultElement; - Position++; + if (PeekByte() == OfEmptyList[0]) + { + result[i] = defaultElement; + Position++; + } + else + { + result[i] = decodeItem(ref this); + } } - else + + if (checkPositions) { - result[i] = decodeItem(ref this); + Check(positionCheck); } - } - if (checkPositions) + return result; + } + catch { - Check(positionCheck); + try + { + DisposeDecodedItems(result, i); + } + finally + { + result.Dispose(); + } + + throw; } - return result; + static void DisposeDecodedItems(ArrayPoolList list, int count) + { + for (int j = 0; j < count; j++) + { + if (list[j] is IDisposable disposable) + { + disposable.Dispose(); + } + } + } } public readonly bool IsNextItemEmptyByteArray() => PeekByte() is EmptyByteArrayByte; From 9636ed7e425fee378b4bab374290bb3b47266f37 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 23 Mar 2026 19:40:05 +0300 Subject: [PATCH 3/3] Catch more; cleanup --- src/Nethermind/Nethermind.Core.Test/RlpTests.cs | 16 ---------------- .../Messages/StorageRangesMessageSerializer.cs | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index 2ee125eb130c..6dc0d45afc26 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -568,21 +568,5 @@ private static byte[] BuildLongFormRlp(int prefix, int contentLength) } return data; } - - private sealed class DisposableTracker(Action onDispose) : IDisposable - { - private bool _disposed; - - public void Dispose() - { - if (_disposed) - { - return; - } - - _disposed = true; - onDispose(); - } - } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs index 13015fe86e5a..fd776d5b6c90 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs @@ -63,11 +63,11 @@ public StorageRangeMessage Deserialize(IByteBuffer byteBuffer) Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startPos = ctx.Position; - ctx.ReadSequenceLength(); - StorageRangeMessage message = new(); + try { + ctx.ReadSequenceLength(); message.RequestId = ctx.DecodeLong(); message.Slots = ctx.DecodeArrayPoolList>(static (ref Rlp.ValueDecoderContext outerCtx) =>