Skip to content

Commit 6d6e14e

Browse files
authored
Handle fork ID and seq in ENRs (#12126)
1 parent 618af12 commit 6d6e14e

35 files changed

Lines changed: 1338 additions & 247 deletions

File tree

src/Nethermind/Nethermind.Config/NetworkNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static NetworkNode[] ParseNodes(string[]? nodeRecords, ILogger logger)
7676
return [.. nodes];
7777
}
7878

79-
public override string ToString() => IsEnode ? Enode.ToString() : Enr.EnrString;
79+
public override string ToString() => IsEnode ? Enode.ToString() : Enr.ToString();
8080

8181
public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0)
8282
: this(new Enode(publicKey, IPAddress.Parse(ip), port)) => Reputation = reputation;

src/Nethermind/Nethermind.Core/Threading/InterlockedEx.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,3 @@ public static ulong Min(ref ulong location, ulong value)
144144
return current;
145145
}
146146
}
147-

src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ private void SetBasicInfo(Peer peer, IReadOnlyList<Capability> capabilities)
6565
Name = peer.Node.ClientId;
6666
Enode = peer.Node.ToString(Node.Format.ENode);
6767
Caps = capabilities;
68-
Enr = peer.Node.Enr;
68+
Enr = peer.Node.Enr?.ToString();
6969
}
7070

7171
private void SetNetworkInfo(Peer peer)

src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryV5AppTests.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using Autofac;
55
using Autofac.Features.AttributeFilters;
6+
using Nethermind.Blockchain;
67
using Nethermind.Config;
8+
using Nethermind.Core;
79
using Nethermind.Core.Crypto;
810
using Nethermind.Core.Test.Builders;
911
using Nethermind.Core.Test.Modules;
@@ -52,13 +54,21 @@ private DiscoveryV5App CreateDiscoveryV5App(IPAddress externalIp, Action<Contain
5254
IEnode enode = new Enode(nodeKey.PublicKey, externalIp, networkConfig.P2PPort, networkConfig.DiscoveryPort);
5355
IIPResolver ipResolver = new FixedIpResolver(networkConfig);
5456
EthereumEcdsa ecdsa = new(0);
57+
IBlockTree blockTree = Substitute.For<IBlockTree>();
58+
Block head = Build.A.Block.Genesis.TestObject;
59+
blockTree.Head.Returns(head);
60+
IForkInfo forkInfo = Substitute.For<IForkInfo>();
61+
forkInfo.GetForkId(head.Header.Number, head.Header.Timestamp).Returns(new Nethermind.Network.ForkId(0, 0));
5562
ContainerBuilder builder = new();
5663
builder.RegisterInstance(LimboLogs.Instance).As<ILogManager>();
5764
builder.RegisterInstance(networkConfig).As<INetworkConfig>();
5865
builder.RegisterInstance(enode).As<IEnode>();
5966
builder.RegisterInstance(ipResolver).As<IIPResolver>();
6067
builder.RegisterInstance(nodeKey).Keyed<IProtectedPrivateKey>(IProtectedPrivateKey.NodeKey);
6168
builder.RegisterInstance(ecdsa).As<IEthereumEcdsa>().As<IEcdsa>();
69+
builder.RegisterInstance(blockTree).As<IBlockTree>();
70+
builder.RegisterInstance(forkInfo).As<IForkInfo>();
71+
builder.RegisterInstance(Timestamper.Default).As<ITimestamper>();
6272
builder.RegisterInstance(new CryptoRandom()).As<ICryptoRandom>();
6373
builder.RegisterInstance(new NetworkStorage(_discoveryDb, LimboLogs.Instance)).Keyed<INetworkStorage>(DbNames.DiscoveryV5Nodes);
6474
builder.RegisterInstance(Substitute.For<INodeStatsManager>()).As<INodeStatsManager>();
@@ -189,7 +199,7 @@ public void Should_Accept_Public_Ip_Enr()
189199
public void Should_Reject_Consensus_Only_Enr()
190200
{
191201
NodeRecord enr = CreateTestEnr(TestItem.PrivateKeyA, IPAddress.Parse("8.8.8.8"), includeEth2: true);
192-
NodeRecord decoded = NodeRecord.FromEnrString(enr.EnrString);
202+
NodeRecord decoded = NodeRecord.FromEnrString(enr.ToString());
193203

194204
bool result = _discoveryV5App.TryGetAcceptableNodeFromEnr(decoded, out Node? node);
195205

@@ -231,7 +241,7 @@ public async Task AddNodeToDiscovery_ShouldAddValidatedEnrNode()
231241
NodeRecord enr = CreateTestEnr(TestItem.PrivateKeyA, IPAddress.Parse("8.8.8.8"), udpPort: 30304);
232242
Node node = new(TestItem.PrivateKeyA.PublicKey, "1.1.1.1", 30303)
233243
{
234-
Enr = enr.EnrString
244+
Enr = enr
235245
};
236246

237247
try
@@ -242,7 +252,7 @@ public async Task AddNodeToDiscovery_ShouldAddValidatedEnrNode()
242252
added.Id.Equals(TestItem.PrivateKeyA.PublicKey) &&
243253
added.Host == "8.8.8.8" &&
244254
added.Port == 30304 &&
245-
added.Enr == enr.EnrString));
255+
added.Enr == enr));
246256
}
247257
finally
248258
{
@@ -260,7 +270,7 @@ public async Task AddNodeToDiscovery_ShouldSkipMismatchedEnr()
260270
NodeRecord enr = CreateTestEnr(TestItem.PrivateKeyA, IPAddress.Parse("8.8.8.8"));
261271
Node node = new(TestItem.PrivateKeyB.PublicKey, "8.8.8.8", 30303)
262272
{
263-
Enr = enr.EnrString
273+
Enr = enr
264274
};
265275

266276
try
@@ -318,7 +328,7 @@ public void Should_Use_Udp_Port_From_Configured_Enr_Bootnode()
318328
NodeRecord enr = CreateTestEnr(TestItem.PrivateKeyA, IPAddress.Parse("8.8.8.8"), udpPort: 9001, includeTcp: false);
319329
NetworkConfig networkConfig = new()
320330
{
321-
Bootnodes = [new NetworkNode(enr.EnrString)]
331+
Bootnodes = [new NetworkNode(enr.ToString())]
322332
};
323333
DiscoveryConfig discoveryConfig = new()
324334
{
@@ -331,7 +341,7 @@ public void Should_Use_Udp_Port_From_Configured_Enr_Bootnode()
331341
{
332342
Assert.That(bootNodes, Has.Count.EqualTo(1));
333343
Assert.That(bootNodes[0].Port, Is.EqualTo(9001));
334-
Assert.That(bootNodes[0].Enr, Is.EqualTo(enr.EnrString));
344+
Assert.That(bootNodes[0].Enr?.ToString(), Is.EqualTo(enr.ToString()));
335345
}
336346
}
337347

src/Nethermind/Nethermind.Network.Discovery.Test/Discv4/DiscoveryMessageSerializerTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,53 @@ public void Ping_with_enr_there_and_back()
134134
Assert.That(pingMsg.EnrSequence, Is.EqualTo(3));
135135
}
136136

137+
[Test]
138+
public void Pong_with_enr_there_and_back()
139+
{
140+
PongMsg pongMsg = new(
141+
new IPEndPoint(TestItem.IPEndPointA.Address, 30303),
142+
long.MaxValue,
143+
TestItem.KeccakA.ValueHash256,
144+
3);
145+
using DisposableByteBuffer serialized = _messageSerializationService.ZeroSerialize(pongMsg).AsDisposable();
146+
pongMsg = _messageSerializationService.Deserialize<PongMsg>(serialized);
147+
Assert.That(pongMsg.EnrSequence, Is.EqualTo(3));
148+
}
149+
150+
[Test]
151+
public void Pong_with_enr_and_future_trailing_value_deserializes()
152+
{
153+
byte[] ip = TestItem.IPEndPointA.Address.GetAddressBytes();
154+
const int port = 30303;
155+
const ulong enrSequence = 3;
156+
const string futureValue = "future";
157+
ValueHash256? pingMdc = TestItem.KeccakA.ValueHash256;
158+
int addressLength = Rlp.LengthOf(ip) + 2 * Rlp.LengthOf(port);
159+
int contentLength =
160+
Rlp.LengthOfSequence(addressLength) +
161+
Rlp.LengthOf(in pingMdc) +
162+
Rlp.LengthOf(long.MaxValue) +
163+
Rlp.LengthOf(enrSequence) +
164+
Rlp.LengthOf(futureValue);
165+
166+
byte[] data = new byte[Rlp.LengthOfSequence(contentLength)];
167+
RlpWriter writer = new(data);
168+
writer.StartSequence(contentLength);
169+
writer.StartSequence(addressLength);
170+
writer.Encode(ip);
171+
writer.Encode(port);
172+
writer.Encode(port);
173+
writer.Encode(in pingMdc);
174+
writer.Encode(long.MaxValue);
175+
writer.Encode(enrSequence);
176+
writer.Encode(futureValue);
177+
178+
PongMsg pongMsg = _messageSerializationService.Deserialize<PongMsg>(
179+
SignAndWrapDiscoveryPacket((byte)MsgType.Pong, data));
180+
181+
Assert.That(pongMsg.EnrSequence, Is.EqualTo(enrSequence));
182+
}
183+
137184
[Test]
138185
public void Enr_request_there_and_back()
139186
{

src/Nethermind/Nethermind.Network.Discovery.Test/Discv4/DiscoveryPersistenceManagerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public async Task RunDiscoveryPersistenceCommit_Should_Preserve_Enr_In_Common_St
184184
NodeRecord enr = TestEnrBuilder.BuildSigned(TestItem.PrivateKeyA, IPAddress.Parse("8.8.8.8"), tcpPort: 30303, udpPort: 30304);
185185
Node node = new(TestItem.PrivateKeyA.PublicKey, "8.8.8.8", 30304)
186186
{
187-
Enr = enr.EnrString
187+
Enr = enr
188188
};
189189

190190
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(10));
@@ -211,7 +211,7 @@ public async Task RunDiscoveryPersistenceCommit_Should_Preserve_Enr_In_Common_St
211211
NodeRecord? persistedEnr = persistedNode.Enr;
212212
Assert.That(persistedNode.IsEnr, Is.True);
213213
Assert.That(persistedEnr, Is.Not.Null);
214-
Assert.That(persistedEnr!.EnrString, Is.EqualTo(enr.EnrString));
214+
Assert.That(persistedEnr!.ToString(), Is.EqualTo(enr.ToString()));
215215
Assert.That(persistedNode.NodeId, Is.EqualTo(TestItem.PrivateKeyA.PublicKey));
216216
Assert.That(persistedNode.Host, Is.EqualTo("8.8.8.8"));
217217
Assert.That(persistedNode.Port, Is.EqualTo(30304));

src/Nethermind/Nethermind.Network.Discovery.Test/Discv4/EIP8DiscoveryTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public void PongFormatTest()
5252
"42124e";
5353
PongMsg pong = _messageSerializationService.Deserialize<PongMsg>(Bytes.FromHexString(encodedPong));
5454
Assert.That(pong.ExpirationTime, Is.EqualTo(1136239445));
55+
Assert.That(pong.EnrSequence, Is.Null);
5556
}
5657

5758
[Test]

src/Nethermind/Nethermind.Network.Discovery.Test/Discv4/Kademlia/KademliaAdapterTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: LGPL-3.0-only
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Linq;
67
using System.Net;
78
using System.Threading;
@@ -176,6 +177,45 @@ private DiscoveryMsg CreateUnsolicitedResponse(MsgType msgType) =>
176177
_ => throw new ArgumentOutOfRangeException(nameof(msgType), msgType, null)
177178
};
178179

180+
private NodeRecord ConfigureRemoteEnrRefresh(ulong advertisedSequence, ulong responseSequence)
181+
{
182+
NodeRecord remoteRecord = TestEnrBuilder.BuildSigned(
183+
TestItem.PrivateKeyB,
184+
IPAddress.Parse("192.168.1.2"),
185+
tcpPort: null,
186+
udpPort: 30303,
187+
enrSequence: responseSequence);
188+
189+
_msgSender
190+
.When(x => x.SendMsg(Arg.Any<PingMsg>()))
191+
.Do(ci =>
192+
{
193+
PingMsg sent = (PingMsg)ci[0]!;
194+
using DisposableByteBuffer buffer = _receiverSerializationManager.ZeroSerialize(sent).AsDisposable();
195+
PingMsg msg = _receiverSerializationManager.Deserialize<PingMsg>(buffer);
196+
PongMsg pong = new(
197+
msg.FarPublicKey!,
198+
_timestamper.UnixTime.SecondsLong + 1,
199+
sent.Mdc!.Value,
200+
advertisedSequence);
201+
pong.FarAddress = _receiver.Address;
202+
Task.Run(() => _adapter.OnIncomingMsg(pong));
203+
});
204+
205+
_msgSender
206+
.When(x => x.SendMsg(Arg.Any<EnrRequestMsg>()))
207+
.Do(ci =>
208+
{
209+
EnrRequestMsg sent = (EnrRequestMsg)ci[0]!;
210+
ValueHash256 requestHash = TestItem.KeccakA.ValueHash256;
211+
sent.Hash = requestHash;
212+
EnrResponseMsg response = AddReceiverFarAddress(new EnrResponseMsg(_receiver.Address, remoteRecord, new Hash256(requestHash)));
213+
Task.Run(() => _adapter.OnIncomingMsg(response));
214+
});
215+
216+
return remoteRecord;
217+
}
218+
179219
[Test]
180220
[CancelAfter(10000)]
181221
public async Task Ping_should_send_ping_and_receive_pong(CancellationToken token)
@@ -261,6 +301,54 @@ public async Task SendEnrRequest_should_reject_unsolicited_response_with_wrong_k
261301
Assert.That(result, Is.Null);
262302
}
263303

304+
private static IEnumerable<TestCaseData> RemoteEnrRefreshCases()
305+
{
306+
yield return new TestCaseData(0UL, 0UL, false, false)
307+
.SetName("Ping_should_not_request_remote_enr_when_pong_has_no_advertised_sequence");
308+
yield return new TestCaseData(2UL, 2UL, true, true)
309+
.SetName("Ping_should_cache_remote_enr_when_response_sequence_matches_advertised_sequence");
310+
yield return new TestCaseData(3UL, 2UL, true, false)
311+
.SetName("Ping_should_not_cache_remote_enr_when_response_sequence_is_below_advertised_sequence");
312+
yield return new TestCaseData(3UL, 4UL, true, true)
313+
.SetName("Ping_should_cache_remote_enr_when_response_sequence_is_above_advertised_sequence");
314+
}
315+
316+
[TestCaseSource(nameof(RemoteEnrRefreshCases))]
317+
[CancelAfter(10000)]
318+
public async Task Ping_should_refresh_remote_enr_from_advertised_sequence(
319+
ulong advertisedSequence,
320+
ulong responseSequence,
321+
bool shouldRequestEnr,
322+
bool shouldCacheEnr,
323+
CancellationToken token)
324+
{
325+
NodeRecord remoteRecord = ConfigureRemoteEnrRefresh(advertisedSequence, responseSequence);
326+
327+
bool result = await _adapter.Ping(_receiver, token);
328+
329+
Assert.That(result, Is.True);
330+
if (shouldRequestEnr)
331+
{
332+
await _msgSender.Received(1).SendMsg(Arg.Is<EnrRequestMsg>(m => m.FarAddress!.Equals(_receiver.Address)));
333+
}
334+
else
335+
{
336+
await _msgSender.DidNotReceive().SendMsg(Arg.Any<EnrRequestMsg>());
337+
}
338+
339+
if (shouldCacheEnr)
340+
{
341+
_kademliaMessageReceiver.Received(1).AddOrRefresh(Arg.Is<Node>(n =>
342+
n.Id.Equals(_receiver.Id) &&
343+
n.Enr != null &&
344+
n.Enr.ToString() == remoteRecord.ToString()));
345+
}
346+
else
347+
{
348+
_kademliaMessageReceiver.DidNotReceive().AddOrRefresh(Arg.Any<Node>());
349+
}
350+
}
351+
264352
[Test]
265353
[CancelAfter(10000)]
266354
public async Task Timed_out_response_handler_should_not_consume_later_unsolicited_message(CancellationToken token)

src/Nethermind/Nethermind.Network.Discovery.Test/Discv5/CodecTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ public void MessageCodec_Roundtrips_Nodes_From_NonZero_ArraySegment()
305305
Assert.That(decodedNodes.RequestId, Is.EqualTo(message.RequestId));
306306
Assert.That(decodedNodes.Total, Is.EqualTo(message.Total));
307307
Assert.That(decodedNodes.Records.Count, Is.EqualTo(1));
308-
Assert.That(decodedNodes.Records[0].EnrString, Is.EqualTo(expectedRecord.EnrString));
308+
Assert.That(decodedNodes.Records[0].ToString(), Is.EqualTo(expectedRecord.ToString()));
309309
}
310310

311311
[Test]
@@ -330,7 +330,7 @@ public void MessageCodec_Skips_Invalid_Enrs_In_Nodes()
330330
Assert.That(decoded, Is.InstanceOf<NodesMsg>());
331331
NodesMsg nodes = (NodesMsg)decoded;
332332
Assert.That(nodes.Records.Count, Is.EqualTo(1));
333-
Assert.That(nodes.Records[0].EnrString, Is.EqualTo(expectedRecord.EnrString));
333+
Assert.That(nodes.Records[0].ToString(), Is.EqualTo(expectedRecord.ToString()));
334334
}
335335

336336
[Test]

0 commit comments

Comments
 (0)