RPC: admin_* peer-management endpoints improvements - #11489
Conversation
|
Claude finished @svlachakis's task in 4m 28s —— View job Code Review
SummaryThe overall direction is good — aligning with Geth semantics, making the API idempotent, and adding upfront validation are all the right calls. The interface docs and PR description are thorough. One correctness issue must be resolved before merge. FindingsHighIncomplete enode validation — unhandled exceptions bypass the
All four methods are affected — Fix: wrap each object construction in a Medium
Low
Breaking-change acknowledgmentThe wire-level changes ( |
|
@claude review again |
|
Claude finished @svlachakis's task in 3m 52s —— View job Code Review (Re-review)
Previous Finding StatusAll previous findings have been addressed:
New FindingsLow
SummaryThe core concerns from the previous review are all resolved. The validation now correctly catches both URI-parse failures and constructor-level failures (bad key hex, bad port, DNS) for all four endpoints. Tests cover both failure classes. The dual-removal comment is clear. The smoke test has been properly replaced. The remaining items are all low-severity and either pre-existing or intentional design choices documented in the PR description. This PR is ready to merge. |
|
@claude review again |
|
Claude finished @svlachakis's task in 3m 5s —— View job Code Review (Re-review #2)
Previous Finding StatusAll findings from prior reviews are resolved:
New FindingsLow
if (updateFile)
{
await SaveFileAsync(cancellationToken); // <-- if CT fires here, throws
}
OnNodeRemoved(networkNode); // <-- never reached on cancellationWhen
SummaryThe core changes are sound. All High and Medium issues from prior reviews are resolved. Validation now correctly converts both URI-parse failures and constructor-level failures to The only new finding is a Low-severity edge case in |
|
@claude review again |
|
Claude finished @svlachakis's task in 2m 59s —— View job Code Review (Re-review #3)
Previous Finding StatusAll findings from prior reviews are resolved:
New FindingsLow
await _staticNodesManager.RemoveAsync(networkNode!, updateFile: persistent, timeout.Token);
_peerPool.TryRemove(networkNode!.NodeId, out _); // fallback for non-static peersIf This is a narrow window — it only matters for discovered peers when try
{
await _staticNodesManager.RemoveAsync(networkNode!, updateFile: persistent, timeout.Token);
}
finally
{
_peerPool.TryRemove(networkNode!.NodeId, out _);
}Not blocking, but easy to address.
SummaryAll High and Medium findings from prior reviews are fully resolved. Validation correctly converts both URI-parse failures and constructor-level failures to The only remaining items are Low-severity edge cases or intentional design choices. This PR is ready to merge. |
|
@claude review again |
|
Claude finished @svlachakis's task in 3m 14s —— View job Code Review (Re-review #4)
Previous Finding StatusAll findings from prior reviews are resolved:
New FindingsLow
This allocation runs on every call — including idempotent re-trusts where
The test correctly verifies that
SummaryAll High and Medium findings from prior reviews are fully resolved. Validation correctly converts both URI-parse failures and constructor-level failures to The remaining items are all Low-severity pre-existing or intentional design choices. This PR is ready to merge. |
|
Claude finished @svlachakis's task in 2m 25s —— View job Code Review (Final — ready_for_review)
Previous Finding StatusAll findings from prior review rounds are confirmed resolved:
New FindingsLow
Both helpers use catch (Exception ex) when (ex is ArgumentException or FormatException or SocketException)Not blocking.
Allocates Correctness ConfirmationThe core behaviours introduced by this PR are correct:
SummaryAll High and Medium findings from prior reviews are fully resolved. The implementation is correct, idempotent, and matches Geth semantics where specified. Test coverage is thorough, including the narrow edge cases. This PR is ready to merge. |
|
@claude review again. This is intentional |
|
Claude finished @svlachakis's task in 2m 24s —— View job Code Review (Re-review #5)
Previous Finding StatusAll findings from prior reviews are confirmed resolved, including the
Acknowledged:
|
|
@claude please check the IsTrusted improvement. Trust is keyed by public key only, matching geth's Server.trusted map (p2p/server.go:644 — trusted[enode.ID()] = true; enode.ID() is keccak256(pubkey), a 1:1 mapping with the pubkey). Host and port are address-book metadata, not part of the trust identity: a trusted peer that reconnects from a different address must still be treated as trusted. |
|
Claude finished @svlachakis's task in 1m 18s —— View job Code Review —
|
| NetworkNode networkNode = new(enode); | ||
| _peerPool.GetOrAdd(new Node(networkNode)); | ||
| added = true; | ||
| _peerPool.TryRemove(networkNode!.NodeId, out _); |
There was a problem hiding this comment.
should we return result of this or always true?
There was a problem hiding this comment.
Geth-parity. Server.RemovePeer is idempotent, admin.RemovePeer always returns true, nil on parsed input regardless of whether the peer existed. Returning TryRemove's bool would break that contract.
| if (_trustedNodesManager.IsTrusted(enodeObj!)) | ||
| { | ||
| removed = _peerPool.TryRemove(new NetworkNode(enode).NodeId, out Peer _); | ||
| return ResultWrapper<bool>.Success(true); |
There was a problem hiding this comment.
Should we potentially skip this check and use result of _trustedNodesManager.AddAsync?
There was a problem hiding this comment.
That's fast path. IsTrusted is one ContainsKey. Without it the re-trust path allocates a CTS, runs the async state machine, allocates a NetworkNode inside AddAsync only to fail TryAdd on duplicate, and allocates another NetworkNode for the GetOrAdd in finally. Restores the short-circuit the original IsTrusted(...) || AddAsync(...) had.
| finally | ||
| { | ||
| return ResultWrapper<bool>.Fail("Failed to add trusted peer."); | ||
| _peerPool.GetOrAdd(new NetworkNode(enodeObj!)); |
There was a problem hiding this comment.
should this happen only if _trustedNodesManager.AddAsync is true?
There was a problem hiding this comment.
that's for Cancellation safety. If AddAsync is cancelled mid-await, control skips straight to finally, added would be false and conditional GetOrAdd would leave the peer in the trusted dict but not in the pool. Unconditional GetOrAdd is no-op when already pooled.
| return ResultWrapper<bool>.Fail("Failed to add trusted peer."); | ||
| _peerPool.GetOrAdd(new NetworkNode(enodeObj!)); | ||
| } | ||
| return ResultWrapper<bool>.Success(true); |
There was a problem hiding this comment.
should this always be true even if above calls fail?
There was a problem hiding this comment.
I think it's only reachable on AddAsync returning. Throws propagate past the return, the JSON-RPC layer reports them as errors. AddAsync returning false (already trusted) is geth-equivalent success, not failure.
Fixes Closes Resolves #11490
Summary
Wire-level breaking changes (all four endpoints)
string → boolonaddPeer/removePeer(alreadyboolon the trusted pair)addToStaticNodes → persistent,removeFromStaticNodes → persistent(consistent across all four)addTrustedPeerandremoveTrustedPeergained the new optionalpersistentparameterDefault-semantic changes (the same call now does something different)
admin_addPeer(enode): now maintains the connection (Geth-equivalent); was a one-shot dialadmin_removePeer(enode): now removes from static set + disconnects + idempotent; was pool-only and reported failure on unknown peersadmin_addTrustedPeer(enode): no longer writes totrusted-nodes.jsonby defaultadmin_removeTrustedPeer(enode): no longer writes totrusted-nodes.jsonby default; now idempotent on unknown peersValidation (all four)
-32602 InvalidParamswith"invalid enode: ..."instead of an opaque internal RPC error.Further Refactoring
IStaticNodesManager/ITrustedNodesManager/NodesManager.SaveFileAsync; dropped string-typed overloads ofIStaticNodesManager.AddAsync/RemoveAsync/IsStatic(test-only, replaced withNetworkNode-typed) to eliminate the double-parse + double-DNS-lookup; consolidated 4 single-method malformed-enode tests into one parameterized[TestCase]covering both scheme-fail and content-fail pathsenode.ToString()round-trip inTrustedNodesManager.RemoveAsyncChanges
admin_addPeeraddToStaticNodes=false)addToStaticNodes=truepersistent=false)persistent=truestatic-nodes.jsonEnode.IsEnode→-32602boolstring(echoes enode)stringboolmatches Gethbooladmin_removePeerremoveFromStaticNodes=false)removeFromStaticNodes=truepersistent=trueTryRemoveTryRemovefallbackstatic-nodes.jsontruetruematches Geth-32602boolstringstringboolmatches Gethbooladmin_addTrustedPeerpersistent=truetrusted-nodes.jsonupdateFile: truehardcoded)_peerPool.GetOrAddFeedFromNodeSourcethrottle)IsTrustedshort-circuitAddAsyncreturn-32602boolboolboolmatches Gethbooladmin_removeTrustedPeerpersistent=truetrusted-nodes.jsonupdateFile: truehardcoded)NodeRemoved → PeerPool.TryRemove → MarkDisconnected)truetruematches Geth-32602boolboolboolmatches GethboolNethermind extensions Geth doesn't have
persistent: trueflagadmin_addPeer,admin_removePeerstatic-nodes.jsonso it survives restartpersistent: trueflagadmin_addTrustedPeer,admin_removeTrustedPeertrusted-nodes.jsonso trust survives restartadmin_addTrustedPeerPeerPool.FeedFromNodeSourcethrottle delay; the peer is in the pool the moment the RPC returnsadmin_removeTrustedPeerNodeRemovedevent chain — operationally cleaner ("force reconnect under non-trusted rules")Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing
Optional. Remove if not applicable.
Documentation
Requires documentation update