|
| 1 | +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited |
| 2 | +// SPDX-License-Identifier: LGPL-3.0-only |
| 3 | + |
| 4 | +using FluentAssertions; |
| 5 | +using Nethermind.Core.Collections; |
| 6 | +using Nethermind.Core.Test.Builders; |
| 7 | +using Nethermind.Network.P2P.Subprotocols.Snap; |
| 8 | +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; |
| 9 | +using Nethermind.Serialization.Rlp; |
| 10 | +using Nethermind.State.Snap; |
| 11 | +using NUnit.Framework; |
| 12 | + |
| 13 | +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages; |
| 14 | + |
| 15 | +[TestFixture, Parallelizable(ParallelScope.All)] |
| 16 | +public class SnapMessageLimitsTests |
| 17 | +{ |
| 18 | + /// <summary> |
| 19 | + /// Response limits must be large enough that a valid message within |
| 20 | + /// <see cref="SnapMessageLimits.MaxResponseBytes"/> (3 MiB) never exceeds the limit. |
| 21 | + /// A too-low limit causes <see cref="RlpLimitException"/>, which disconnects and bans |
| 22 | + /// the peer for 15 minutes — silently killing SnapSync throughput. |
| 23 | + /// </summary> |
| 24 | + [TestCase(nameof(SnapMessageLimits.MaxResponseAccounts), SnapMessageLimits.MaxResponseAccounts, 40, TestName = "MaxResponseAccounts accommodates 3 MiB at ~40 bytes/account")] |
| 25 | + [TestCase(nameof(SnapMessageLimits.MaxResponseSlotsPerAccount), SnapMessageLimits.MaxResponseSlotsPerAccount, 36, TestName = "MaxResponseSlotsPerAccount accommodates 3 MiB at ~36 bytes/slot")] |
| 26 | + public void Response_limit_accommodates_max_response_bytes(string limitName, int limit, int minEntryBytes) |
| 27 | + { |
| 28 | + int maxTheoreticalItems = (int)(SnapMessageLimits.MaxResponseBytes / minEntryBytes); |
| 29 | + |
| 30 | + limit.Should().BeGreaterThanOrEqualTo(maxTheoreticalItems, |
| 31 | + "{0} must accommodate the maximum item count that fits in a {1}-byte response at {2} bytes/entry", |
| 32 | + limitName, SnapMessageLimits.MaxResponseBytes, minEntryBytes); |
| 33 | + } |
| 34 | + |
| 35 | + [Test] |
| 36 | + public void Roundtrip_AccountRange_at_40k_accounts() |
| 37 | + { |
| 38 | + const int count = 40_000; |
| 39 | + AccountRangeMessageSerializer serializer = new(); |
| 40 | + |
| 41 | + ArrayPoolList<PathWithAccount> accounts = new(count); |
| 42 | + for (int i = 0; i < count; i++) |
| 43 | + { |
| 44 | + accounts.Add(new PathWithAccount(TestItem.KeccakA, Build.An.Account.WithBalance(1).TestObject)); |
| 45 | + } |
| 46 | + |
| 47 | + AccountRangeMessage msg = new() |
| 48 | + { |
| 49 | + RequestId = 1, |
| 50 | + PathsWithAccounts = accounts, |
| 51 | + Proofs = EmptyByteArrayList.Instance, |
| 52 | + }; |
| 53 | + |
| 54 | + byte[] serialized = serializer.Serialize(msg); |
| 55 | + AccountRangeMessage deserialized = serializer.Deserialize(serialized); |
| 56 | + |
| 57 | + deserialized.PathsWithAccounts.Count.Should().Be(count); |
| 58 | + } |
| 59 | + |
| 60 | + [Test] |
| 61 | + public void Roundtrip_StorageRange_at_50k_slots() |
| 62 | + { |
| 63 | + const int slotCount = 50_000; |
| 64 | + StorageRangesMessageSerializer serializer = new(); |
| 65 | + |
| 66 | + ArrayPoolList<PathWithStorageSlot> slots = new(slotCount); |
| 67 | + for (int i = 0; i < slotCount; i++) |
| 68 | + { |
| 69 | + slots.Add(new PathWithStorageSlot(TestItem.KeccakA, Rlp.Encode(new byte[] { 0x01 }).Bytes)); |
| 70 | + } |
| 71 | + |
| 72 | + using StorageRangeMessage msg = new() |
| 73 | + { |
| 74 | + RequestId = 1, |
| 75 | + Slots = new ArrayPoolList<IOwnedReadOnlyList<PathWithStorageSlot>>(1) { slots }, |
| 76 | + Proofs = new ByteArrayListAdapter(ArrayPoolList<byte[]>.Empty()), |
| 77 | + }; |
| 78 | + |
| 79 | + byte[] serialized = serializer.Serialize(msg); |
| 80 | + using StorageRangeMessage deserialized = serializer.Deserialize(serialized); |
| 81 | + |
| 82 | + deserialized.Slots[0].Count.Should().Be(slotCount); |
| 83 | + } |
| 84 | +} |
0 commit comments