-
Notifications
You must be signed in to change notification settings - Fork 1k
Add RoutingTable for DHT #4422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+924
−13
Merged
Add RoutingTable for DHT #4422
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
309a679
Add RoutingTable for DHT
erikzhang 6ee3c92
Merge branch 'master' into RoutingTable
ajara87 f143c30
Merge branch 'master' into RoutingTable
ajara87 3ef1b40
Add UTs for UInt256 XOR (#5)
ajara87 b1d175c
Merge branch 'master' into RoutingTable
ajara87 1c960fb
Merge branch 'master' into RoutingTable
shargon 4f2f9f2
Add TransportProtocol and EndpointKind
erikzhang aad52c7
MarkSuccess on Ping/Pong message received
erikzhang f68d5fd
Add DisconnectReason
erikzhang f4abe75
Merge branch 'master' into RoutingTable
erikzhang 90f2b65
Merge branch 'master' into RoutingTable
erikzhang 595c051
Merge branch 'master' into RoutingTable
ajara87 7776075
Merge branch 'master' into RoutingTable
Jim8y 71b65df
Add MaxEndpoints
erikzhang 86c823d
Apply suggestion from @shargon
erikzhang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (C) 2015-2026 The Neo Project. | ||
| // | ||
| // DisconnectReason.cs file belongs to the neo project and is free | ||
| // software distributed under the MIT software license, see the | ||
| // accompanying file LICENSE in the main directory of the | ||
| // repository or http://www.opensource.org/licenses/mit-license.php | ||
| // for more details. | ||
| // | ||
| // Redistribution and use in source and binary forms with or without | ||
| // modifications are permitted. | ||
|
|
||
| namespace Neo.Network.P2P; | ||
|
|
||
| /// <summary> | ||
| /// Specifies the reason for a disconnection event in a network or communication context. | ||
| /// </summary> | ||
| /// <remarks>Use this enumeration to determine why a connection was terminated.</remarks> | ||
| public enum DisconnectReason | ||
| { | ||
| /// <summary> | ||
| /// No specific reason for disconnection. | ||
| /// </summary> | ||
| None, | ||
|
|
||
| /// <summary> | ||
| /// The connection was closed normally. | ||
| /// </summary> | ||
| Close, | ||
|
|
||
| /// <summary> | ||
| /// The connection was closed due to a timeout. | ||
| /// </summary> | ||
| Timeout, | ||
|
|
||
| /// <summary> | ||
| /// The connection was closed due to a protocol violation. | ||
| /// </summary> | ||
| ProtocolViolation | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Copyright (C) 2015-2026 The Neo Project. | ||
| // | ||
| // EndpointKind.cs file belongs to the neo project and is free | ||
| // software distributed under the MIT software license, see the | ||
| // accompanying file LICENSE in the main directory of the | ||
| // repository or http://www.opensource.org/licenses/mit-license.php | ||
| // for more details. | ||
| // | ||
| // Redistribution and use in source and binary forms with or without | ||
| // modifications are permitted. | ||
|
|
||
| namespace Neo.Network.P2P; | ||
|
|
||
| /// <summary> | ||
| /// Describes how an overlay endpoint was learned and how it should be used. | ||
| /// </summary> | ||
| [Flags] | ||
| public enum EndpointKind : byte | ||
| { | ||
| /// <summary> | ||
| /// The endpoint was observed as the remote endpoint of an incoming or | ||
| /// outgoing connection. | ||
| /// This usually represents a NAT-mapped or ephemeral public endpoint and | ||
| /// should NOT be treated as a reliable dial target. | ||
| /// </summary> | ||
| Observed = 1, | ||
|
|
||
| /// <summary> | ||
| /// The endpoint was explicitly advertised by the peer itself, typically | ||
| /// via protocol handshake metadata (e.g., Version/Listener port). | ||
| /// | ||
| /// Advertised endpoints indicate that the peer claims to be listening | ||
| /// for incoming connections on this address and port. | ||
| /// | ||
| /// Actual reachability is still validated through success/failure tracking. | ||
| /// </summary> | ||
| Advertised = 2, | ||
|
|
||
| /// <summary> | ||
| /// The endpoint was derived indirectly rather than directly observed or | ||
| /// self-advertised. | ||
| /// | ||
| /// Derived endpoints usually require validation before being trusted for | ||
| /// active communication. | ||
| /// </summary> | ||
| Derived = 4, | ||
|
|
||
| /// <summary> | ||
| /// The endpoint represents a relay or intermediary rather than a direct | ||
| /// network address of the target node. | ||
| /// | ||
| /// Relay endpoints are never used for direct dialing and require | ||
| /// protocol-specific relay support. | ||
| /// </summary> | ||
| Relay = 8 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| // Copyright (C) 2015-2026 The Neo Project. | ||
| // | ||
| // KBucket.cs file belongs to the neo project and is free | ||
| // software distributed under the MIT software license, see the | ||
| // accompanying file LICENSE in the main directory of the | ||
| // repository or http://www.opensource.org/licenses/mit-license.php | ||
| // for more details. | ||
| // | ||
| // Redistribution and use in source and binary forms with or without | ||
| // modifications are permitted. | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace Neo.Network.P2P; | ||
|
|
||
| /// <summary> | ||
| /// A Kademlia-style k-bucket: stores up to <see cref="Capacity"/> contacts in LRU order. | ||
| /// </summary> | ||
| sealed class KBucket | ||
| { | ||
| private readonly LinkedList<NodeContact> _lru = new(); | ||
| private readonly Dictionary<UInt256, LinkedListNode<NodeContact>> _index = new(); | ||
|
|
||
| // Replacement cache: best-effort candidates when the bucket is full. | ||
| private readonly LinkedList<NodeContact> _replacements = new(); | ||
| private readonly Dictionary<UInt256, LinkedListNode<NodeContact>> _repIndex = new(); | ||
|
|
||
| public int Capacity { get; } | ||
| public int ReplacementCapacity { get; } | ||
| public int BadThreshold { get; } | ||
| public int Count => _lru.Count; | ||
| public IReadOnlyCollection<NodeContact> Contacts => _lru; | ||
|
|
||
| public KBucket(int capacity, int replacementCapacity, int badThreshold) | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); | ||
| ArgumentOutOfRangeException.ThrowIfNegative(replacementCapacity); | ||
| ArgumentOutOfRangeException.ThrowIfNegativeOrZero(badThreshold); | ||
| Capacity = capacity; | ||
| ReplacementCapacity = replacementCapacity; | ||
| BadThreshold = badThreshold; | ||
| } | ||
|
|
||
| public bool TryGet(UInt256 nodeId, [NotNullWhen(true)] out NodeContact? contact) | ||
| { | ||
| if (_index.TryGetValue(nodeId, out var node)) | ||
| { | ||
| contact = node.Value; | ||
| return true; | ||
| } | ||
| contact = null; | ||
| return false; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Updates LRU position and contact metadata. If bucket is full and the node is new, | ||
| /// the node is placed into replacement cache. | ||
| /// </summary> | ||
| /// <returns> | ||
| /// True if the contact ended up in the main bucket; false if it was cached as a replacement. | ||
| /// </returns> | ||
| public bool Update(NodeContact incoming) | ||
| { | ||
| if (_index.TryGetValue(incoming.NodeId, out var existingNode)) | ||
| { | ||
| Merge(existingNode.Value, incoming); | ||
| Touch(existingNode); | ||
| return true; | ||
| } | ||
|
|
||
| if (_lru.Count < Capacity) | ||
| { | ||
| var node = _lru.AddLast(incoming); | ||
| _index[incoming.NodeId] = node; | ||
| return true; | ||
| } | ||
|
|
||
| // Bucket full: keep as replacement candidate. | ||
| AddOrUpdateReplacement(incoming); | ||
| return false; | ||
| } | ||
|
|
||
| public void MarkSuccess(UInt256 nodeId) | ||
| { | ||
| if (_index.TryGetValue(nodeId, out var node)) | ||
| { | ||
| node.Value.FailCount = 0; | ||
| node.Value.LastSeen = TimeProvider.Current.UtcNow; | ||
| Touch(node); | ||
| return; | ||
| } | ||
|
|
||
| // If it was only a replacement, promote its freshness. | ||
| if (_repIndex.TryGetValue(nodeId, out var repNode)) | ||
| { | ||
| repNode.Value.FailCount = 0; | ||
| repNode.Value.LastSeen = TimeProvider.Current.UtcNow; | ||
| Touch(repNode); | ||
| } | ||
| } | ||
|
|
||
| public void MarkFailure(UInt256 nodeId) | ||
| { | ||
| if (_index.TryGetValue(nodeId, out var node)) | ||
| { | ||
| node.Value.FailCount++; | ||
| if (node.Value.FailCount < BadThreshold) return; | ||
|
|
||
| // Evict bad node and promote best replacement (if any). | ||
| RemoveFrom(node, _index); | ||
| PromoteReplacementIfAny(); | ||
| } | ||
| else if (_repIndex.TryGetValue(nodeId, out var repNode)) | ||
| { | ||
| // If it is a replacement, decay it and possibly drop. | ||
| repNode.Value.FailCount++; | ||
| if (repNode.Value.FailCount >= BadThreshold) | ||
| RemoveFrom(repNode, _repIndex); | ||
| } | ||
| } | ||
|
|
||
| public void Remove(UInt256 nodeId) | ||
| { | ||
| if (_index.TryGetValue(nodeId, out var node)) | ||
| { | ||
| RemoveFrom(node, _index); | ||
| PromoteReplacementIfAny(); | ||
| } | ||
| else if (_repIndex.TryGetValue(nodeId, out var repNode)) | ||
| { | ||
| RemoveFrom(repNode, _repIndex); | ||
| } | ||
| } | ||
|
|
||
| void AddOrUpdateReplacement(NodeContact incoming) | ||
| { | ||
| if (_repIndex.TryGetValue(incoming.NodeId, out var existing)) | ||
| { | ||
| Merge(existing.Value, incoming); | ||
| Touch(existing); | ||
| return; | ||
| } | ||
|
|
||
| if (ReplacementCapacity == 0) return; | ||
|
|
||
| var node = _replacements.AddLast(incoming); | ||
| _repIndex[incoming.NodeId] = node; | ||
|
|
||
| if (_replacements.Count > ReplacementCapacity) | ||
| { | ||
| // Drop oldest replacement. | ||
| var first = _replacements.First; | ||
| if (first is not null) | ||
| RemoveFrom(first, _repIndex); | ||
| } | ||
| } | ||
|
|
||
| void PromoteReplacementIfAny() | ||
| { | ||
| if (_lru.Count >= Capacity) return; | ||
| if (_replacements.Last is null) return; | ||
|
|
||
| // Promote the most recently seen replacement. | ||
| var rep = _replacements.Last; | ||
| RemoveFrom(rep, _repIndex); | ||
| var main = _lru.AddLast(rep.Value); | ||
| _index[main.Value.NodeId] = main; | ||
| } | ||
|
|
||
| static void Merge(NodeContact dst, NodeContact src) | ||
| { | ||
| // Merge overlay endpoints (preserve transport; merge endpoint kinds). | ||
| for (int i = 0; i < src.Endpoints.Count; i++) | ||
| dst.AddOrPromoteEndpoint(src.Endpoints[i]); | ||
|
|
||
| // Prefer latest seen & features. | ||
| if (src.LastSeen > dst.LastSeen) dst.LastSeen = src.LastSeen; | ||
| dst.Features |= src.Features; | ||
| } | ||
|
|
||
| static void Touch(LinkedListNode<NodeContact> node) | ||
| { | ||
| var list = node.List!; | ||
| list.Remove(node); | ||
| list.AddLast(node); | ||
| } | ||
|
|
||
| static void RemoveFrom(LinkedListNode<NodeContact> node, Dictionary<UInt256, LinkedListNode<NodeContact>> index) | ||
| { | ||
| index.Remove(node.Value.NodeId); | ||
| node.List!.Remove(node); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.