-
Notifications
You must be signed in to change notification settings - Fork 1k
[Fix] Improve peer bootstrap connections #4263
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
base: dev
Are you sure you want to change the base?
Conversation
Co-authored-by: Shargon <[email protected]>
Co-authored-by: Shargon <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the OnTimer method in the Peer class to improve the logic for managing peer connections and adds a test case to verify that unconnected peers are not drained when the connecting capacity is zero.
- Refactored
OnTimermethod to use a more sophisticated connection management strategy with multiple capacity checks - Added logic to handle bootstrap scenarios when there are no connected peers
- Introduced a test case to verify behavior when connecting capacity is exhausted
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Neo/Network/P2P/Peer.cs | Refactored OnTimer method with improved connection capacity logic and multiple guard clauses |
| tests/Neo.UnitTests/Network/P2P/UT_LocalNode.cs | Added test case to verify unconnected peers aren't modified when connecting capacity is zero |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| var maxConnections = Config.MaxConnections < 0 | ||
| ? int.MaxValue | ||
| : Config.MaxConnections - ConnectedPeers.Count; |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential negative value if ConnectedPeers.Count > Config.MaxConnections. While the subsequent check on line 386 guards against this, it would be clearer to ensure maxConnections is never negative: Math.Max(0, Config.MaxConnections - ConnectedPeers.Count).
| : Config.MaxConnections - ConnectedPeers.Count; | |
| : Math.Max(0, Config.MaxConnections - ConnectedPeers.Count); |
| typeof(Peer).GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .SetValue(localNode, unconnected); | ||
| typeof(Peer).GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .SetValue(localNode, connecting); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Repeated reflection binding flags should be extracted into a constant to reduce duplication and improve readability. Consider: const BindingFlags privateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
| typeof(Peer).GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .SetValue(localNode, unconnected); | ||
| typeof(Peer).GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .SetValue(localNode, connecting); | ||
|
|
||
| typeof(Peer).GetMethod("OnTimer", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .Invoke(localNode, null); | ||
|
|
||
| var updatedUnconnected = (ImmutableHashSet<IPEndPoint>)typeof(Peer) | ||
| .GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .GetValue(localNode); | ||
| var updatedConnecting = (ImmutableHashSet<IPEndPoint>)typeof(Peer) | ||
| .GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | ||
| .GetValue(localNode); |
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null-check on reflection result. GetField can return null if the field doesn't exist. Consider adding null-forgiving operator or assertion: typeof(Peer).GetField(...)?.SetValue(...) or add an assertion after retrieval to fail early if the field is not found.
| typeof(Peer).GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .SetValue(localNode, unconnected); | |
| typeof(Peer).GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .SetValue(localNode, connecting); | |
| typeof(Peer).GetMethod("OnTimer", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .Invoke(localNode, null); | |
| var updatedUnconnected = (ImmutableHashSet<IPEndPoint>)typeof(Peer) | |
| .GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .GetValue(localNode); | |
| var updatedConnecting = (ImmutableHashSet<IPEndPoint>)typeof(Peer) | |
| .GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .GetValue(localNode); | |
| var unconnectedPeersField = typeof(Peer).GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); | |
| Assert.IsNotNull(unconnectedPeersField, "Field 'UnconnectedPeers' not found in Peer"); | |
| unconnectedPeersField.SetValue(localNode, unconnected); | |
| var connectingPeersField = typeof(Peer).GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); | |
| Assert.IsNotNull(connectingPeersField, "Field 'ConnectingPeers' not found in Peer"); | |
| connectingPeersField.SetValue(localNode, connecting); | |
| typeof(Peer).GetMethod("OnTimer", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| .Invoke(localNode, null); | |
| var unconnectedPeersField2 = typeof(Peer).GetField("UnconnectedPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); | |
| Assert.IsNotNull(unconnectedPeersField2, "Field 'UnconnectedPeers' not found in Peer"); | |
| var updatedUnconnected = (ImmutableHashSet<IPEndPoint>)unconnectedPeersField2.GetValue(localNode); | |
| var connectingPeersField2 = typeof(Peer).GetField("ConnectingPeers", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); | |
| Assert.IsNotNull(connectingPeersField2, "Field 'ConnectingPeers' not found in Peer"); | |
| var updatedConnecting = (ImmutableHashSet<IPEndPoint>)connectingPeersField2.GetValue(localNode); |
shargon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should go to master
| if (deficit > 0 && UnconnectedPeers.Count == 0) | ||
| { | ||
| NeedMorePeers(deficit); | ||
| if (UnconnectedPeers.Count == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is NeedMorePeers a Sync function?
I think assyncrounous I think. So, the if check does not would make much sense here, it was just verified above.
| NeedMorePeers(Config.MinDesiredConnections - ConnectedPeers.Count); | ||
| return; | ||
|
|
||
| var maxConnections = Config.MaxConnections < 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config.MaxConnections < 0, how can this can lower than 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me maxConnections here should be something like currentMaxConnectionsLimit
| var maxConnections = Config.MaxConnections < 0 | ||
| ? int.MaxValue | ||
| : Config.MaxConnections - ConnectedPeers.Count; | ||
| if (maxConnections <= 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConnectedPeers.Count should never be great than Config.MaxConnections, this case should not happen
| if (connectingCapacity <= 0) | ||
| return; | ||
|
|
||
| var toConnect = Math.Max(deficit, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if deficit is 0 and you reached here it is because ConnectingPeers.Count > 0
So, perhaps this needs to be considered
| return; | ||
|
|
||
| var maxConnections = Config.MaxConnections < 0 | ||
| ? int.MaxValue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int.MaxValue ?
vncoelho
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix looks good @Jim8y, there are some checks and parameters that could improve and some are a little strange to me.
i checked your comments, many of them are reasonable range checks, will udpate. |
Description
Restore healthy bootstrap behaviour after the stash fix by letting
Peer.OnTimer()pull a fuller batch of seed peers before honoring lowMinDesiredConnections. That way private-net consensus still convergeswithout depending on the accidental disconnects the race condition used to
cause.
Fixes # N/A
Type of change
standard purpose)
to not work as expected)
Test Configuration:
dotnet test tests/Neo.UnitTests/Neo.UnitTests.csproj -c Release(failsbefore running: MSBuild cannot bind its named pipe in this sandbox—
SocketException (permission denied)).
Checklist:
works
modules