Adds an optional peer_ids parameter to getpeerinfo to filter results by peer IDs.
This is useful when using bitcoin-cli or the console to inspect specific peers referenced in debug.log.
Adds an optional peer_ids parameter to getpeerinfo to filter results by peer IDs.
This is useful when using bitcoin-cli or the console to inspect specific peers referenced in debug.log.
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--006a51241073e994b41acfe9ec718e94-->
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32741.
<!--021abf342d371248e50ceaed478a90ca-->
See the guideline for information on the review process.
| Type | Reviewers |
|---|---|
| ACK | danielabrozzoni, rkrux |
| Concept NACK | stickies-v |
If your review is incorrectly listed, please copy-paste <code><!--meta-tag:bot-skip--></code> into the comment that the bot should ignore.
<!--174a7506f384e20aa4161008e828411d-->
No conflicts as of last run.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
121 | @@ -122,7 +122,9 @@ static RPCHelpMan getpeerinfo() 122 | return RPCHelpMan{ 123 | "getpeerinfo", 124 | "Returns data about each connected network peer as a json array of objects.", 125 | - {}, 126 | + { 127 | + {"nodeid", RPCArg::Type::NUM, RPCArg::Default{-1}, "Filter results to the peer with this nodeid. Specify -1 to return all peers."}
this should be optional, not a magic default value
Updated to use Optional::OMITTED to avoid relying on magic default value. Thanks!
203 | + HelpExampleRpc("getpeerinfo", "")
204 | + + HelpExampleRpc("getpeerinfo", "7")
205 | },
206 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
207 | {
208 | + const int filterPeerId = request.params[0].isNull() ? -1 : request.params[0].getInt<int>();
nit: snake_case for new code
Also, you can use the self.Arg to get the value.
Squashed commits. Renamed to filter_id. Evaluating how to best use self.Arg with NodeId. Will follow up
Also, you can use the
self.Argto get the value.
Would this approach be preferable to avoid using a sentinel value like -1?
const UniValue* nodeid_arg = self.MaybeArg<UniValue>("nodeid");
const std::optional<NodeId> filter_id = nodeid_arg ? std::make_optional(nodeid_arg->getInt<NodeId>()) : std::nullopt;
if (filter_id && stats.nodeid != *filter_id) continue;
...
if (filter_id && stats.nodeid == *filter_id) break;
Would it be acceptable to touch util.cpp for this to add a int64_t template for MaybeArg or stick to the sentinel approach?
TMPL_INST(nullptr, std::optional<int64_t>, maybe_arg ? std::optional{maybe_arg->getInt<int64_t>()} : std::nullopt;);
Please squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits
Also, missing tests?
I'm frequently using something like bitcoin-cli getpeerinfo | jq '[.[] | select(.id == 24430)]' for this. Doesn't work in the GUI console though.
Added test coverage with nodeid filter in test_getpeerinfo, including a check for nonexistent ids. Let me know if you'd prefer this in a separate test method or different cases
<!--85328a0da195eb286784d51f73fa0af9-->
🚧 At least one of the CI tasks failed.
<sub>Task lint: https://github.com/bitcoin/bitcoin/runs/44049534572</sub>
<sub>LLM reason (✨ experimental): The CI failure is caused by lint errors, specifically a trailing whitespace issue detected during the lint check.</sub>
<details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
187 | + assert_equal(filtered[0]["id"], peer["id"]) 188 | + self.log.debug("Filtered peer id {}: OK".format(peer["id"])) 189 | + 190 | + self.log.info("Check getpeerinfo with nonexistent nodeid filter") 191 | + nonexistent_id = max(p["id"] for p in all_peers) + 1000 192 | + assert_equal(self.nodes[0].getpeerinfo(nonexistent_id), [])
Would it make sense to throw a JSONRPCError(RPC_MISC_ERROR,"Peer does not exist") if the peer does not exist, Similar to the getblockfrompeer instead of sending back an empty array?
where error is thrown in PeerManagerImpl::FetchBlock
I'd lean toward keeping this behavior for consistency unless there's a broader plan to split these use cases into distinct RPCs (say: getpeerbyid). The name getpeerinfo is arguably a bit misleading since it returns a list of peers, but given that it's established, returning an empty array when filtering seems appropriate to me.
If others think we should return an empty array then sounds good to me!
Small touch up in code style, renamed parameter to peer_id for consistency
Concept ACK, I think this is a good addition, as introducing a peer_id parameter to filter the getpeerinfo response is useful for debugging.
This needs a release note :)
307 | @@ -300,6 +308,9 @@ static RPCHelpMan getpeerinfo() 308 | obj.pushKV("session_id", stats.m_session_id); 309 | 310 | ret.push_back(std::move(obj)); 311 | + if (opt_peer_id && stats.nodeid == *opt_peer_id) {
Only need to check opt_peer_id here, since we'd never get here if it's set to something different
First condition checks if the parameter is defined so it returns all results even if peer_id is 0
?
121 | @@ -122,7 +122,9 @@ static RPCHelpMan getpeerinfo() 122 | return RPCHelpMan{ 123 | "getpeerinfo", 124 | "Returns data about each connected network peer as a json array of objects.", 125 | - {}, 126 | + { 127 | + {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Only return info for the specified peer."}
Since we have to iterate peers to find the right one, maybe this should be a list (unordered_set in C++)
Since we have to iterate peers to find the right one, maybe this should be a list (unordered_set in C++)
I agree, the last push swapped to an unordered_set with an array argument
121 | @@ -122,7 +122,9 @@ static RPCHelpMan getpeerinfo()
122 | return RPCHelpMan{
123 | "getpeerinfo",
124 | "Returns data about each connected network peer as a json array of objects.",
Can mention in the RPC help.
- "Returns data about each connected network peer as a json array of objects.",
+ "Returns data about each connected network peer as a json array of objects.\n"
+ "Optionally filter by peer index.\n",
Added line to help text, preferred using peer id naming instead of index, to avoid confusion with the array index
Concept ACK
Useful addition, I can see myself using the filtering here.
As mentioned in #32741#pullrequestreview-2953107634, a release note would be helpful as per this: https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#release-notes
<!--85328a0da195eb286784d51f73fa0af9-->
🚧 At least one of the CI tasks failed.
<sub>Task lint: https://github.com/bitcoin/bitcoin/runs/45364101888</sub>
<sub>LLM reason (✨ experimental): Lint check failed due to a coding style error in rpc_net.py caused by an unnecessary semicolon.</sub>
<details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
Rebased on top of master, updated help text
Updated to use peer_ids (array) instead of a single peer_id. This allows filtering by multiple peers in one call and aligns better with RPC design. Functional tests adjusted.
I would do peer_id|peer_ids and accept both a single number (most common use) or an array (efficient).
I would do peer_id|peer_ids and accept both a single number (most common use) or an array (efficient).
I don't think RPCArg currently supports a single argument that can be either a num or an array (Maybe RPCArg::Type::ANY?). One option would be to add two separate optional arguments: one for a single ID and another for the list, then merge both into the filter set internally. That would allow getpeerinfo 7 for convenience and getpeerinfo -1 '[1,3,4,7]' for multiple.
Figured it out in the latest push, skipped type checking for peer_ids
I would do peer_id|peer_ids and accept both a single number (most common use) or an array (efficient).
Skipped type checking and added simple cases so getpeerinfo 7 and getpeerinfo '[1,2,3]' should work
<!--85328a0da195eb286784d51f73fa0af9-->
🚧 At least one of the CI tasks failed.
<sub>Task lint: https://github.com/bitcoin/bitcoin/runs/47411071680</sub>
<sub>LLM reason (✨ experimental): The CI failure caused by a lint check for a missing trailing newline in the release notes markdown file.</sub>
<details><summary>Hints</summary>
Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
</details>
130 | + "A JSON array of peer IDs, or a single peer ID as a number.\n" 131 | + "If omitted, returns all peers.\n", 132 | + { 133 | + {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Peer ID"}, 134 | + }, 135 | + {RPCArgOptions{.skip_type_check = true}}
{RPCArgOptions{.skip_type_check = true, .type_str = {"", "numeric or array"}}}
Added to changeset
120 | @@ -121,8 +121,18 @@ static RPCHelpMan getpeerinfo() 121 | { 122 | return RPCHelpMan{ 123 | "getpeerinfo", 124 | - "Returns data about each connected network peer as a json array of objects.", 125 | - {}, 126 | + "Returns data about each connected network peer as a json array of objects.\n" 127 | + "Optionally filter by peer ids.\n", 128 | + { 129 | + {"peer_ids", RPCArg::Type::ARR, RPCArg::Optional::OMITTED,
{"peer_id|peer_ids", RPCArg::Type::ARR, RPCArg::Optional::OMITTED,
RPCArg doesn't support '|' in names and will throw a rpc error when it gets called. https://github.com/bitcoin/bitcoin/blob/73220fc0f958f9b65f66cf0cf042af220b312fc6/src/rpc/util.cpp#L923
Rebased on current master.
198 | + 199 | + self.log.info("Check getpeerinfo with nonexistent peer_id") 200 | + nonexistent_id = max(p["id"] for p in all_peers) + 1000 201 | + assert_equal(node.getpeerinfo(nonexistent_id), []) 202 | + assert_raises_rpc_error(-8, "must be a number or an array of numbers", node.getpeerinfo, {}) 203 | +
Nit if retouched:
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 209e804021..47834c8375 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -200,6 +200,7 @@ class NetTest(BitcoinTestFramework):
nonexistent_id = max(p["id"] for p in all_peers) + 1000
assert_equal(node.getpeerinfo(nonexistent_id), [])
assert_raises_rpc_error(-8, "must be a number or an array of numbers", node.getpeerinfo, {})
+ assert_equal(len(node.getpeerinfo([ids[0], nonexistent_id])), 1)
no_version_peer.peer_disconnect()
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 2)
crACK 599628ab9c23cd53a30fcbcb5d4ac370bb536547
the commit message is wrong?
Thanks. Must've replaced it by mistake when I rebased. Fixed now
120 | @@ -121,8 +121,18 @@ static RPCHelpMan getpeerinfo() 121 | { 122 | return RPCHelpMan{ 123 | "getpeerinfo", 124 | - "Returns data about each connected network peer as a json array of objects.", 125 | - {}, 126 | + "Returns data about each connected network peer as a json array of objects.\n" 127 | + "Optionally filter by peer ids.\n",
nit if retouched: "peer IDs" (so it stays consistent with the rest of the help text)
tACK 9b6090d8d985f3ce61eaa587511bbeeeedf28cb9
I left a micro-nit, but I'd say avoid fixing it unless you already need to update, so you don't invalidate the ack :)
lgtm re-ACK 9b6090d8d985f3ce61eaa587511bbeeeedf28cb9
Tested Ack
2026-03-08T22:42:05.369198Z TestFramework (INFO): Test getpeerinfo
2026-03-08T22:42:08.423239Z TestFramework (INFO): Check getpeerinfo output before a version message was sent
2026-03-08T22:42:08.476781Z TestFramework (INFO): Check getpeerinfo filtering: all, single, multiple
2026-03-08T22:42:08.479393Z TestFramework (INFO): Check getpeerinfo with nonexistent peer_id
Concept NACK. This seems easy enough to address client-side such as withjq as already suggested earlier. If there are use cases I'm missing, please document them in OP.
Concept NACK. This seems easy enough to address client-side such as with
jqas already suggested earlier. If there are use cases I'm missing, please document them in OP.
As mentioned jq and command line tools don't work in the qt console. And generally it's just bad to serialize and send the whole peer list when you're only interested in specific ones. I'm sure in a lot of cases it's easier to depend on command line tools but that doesn't mean you shouldn't have targeted fetches.
Anyway cheers thanks for the 'review'
And generally it's just bad to serialize and send the whole peer list when you're only interested in specific ones
If/when serialization overhead becomes meaningful, sure, it can make sense to add server-side filters. Until we're there, this is just unnecessary complexity.
As mentioned jq and command line tools don't work in the qt console.
That sounds like a gui / console issue more than an rpc issue? If the rpc is not the blocker, we shouldn't change the rpc.
Anyway cheers thanks for the 'review'
I'm not sure being dismissive of people spending time on your PR is going to be helpful for your making progress.
And generally it's just bad to serialize and send the whole peer list when you're only interested in specific ones
If/when serialization overhead becomes meaningful, sure, it can make sense to add server-side filters. Until we're there, this is just unnecessary complexity.
Serialization overhead is probably the only 'meaningful' bit out of all the get peer info functionality there is in the code. (Outside of being subjective) In both cpu time, memory and transmission if there is any.
As mentioned jq and command line tools don't work in the qt console.
That sounds like a gui / console issue more than an rpc issue? If the rpc is not the blocker, we shouldn't change the rpc.
Anyway cheers thanks for the 'review'
I'm not sure being dismissive of people spending time on your PR is going to be helpful for your making progress.
I wasn't being dismissive of you 'spending time' on 'my pr'. I was being thankful for your suggestion of using jq and depend on command line utilities instead of having it filtered server side. I'm not sure being passive aggressive in a PR review is going to be helpful to me, muh progress, or yours.
Again thanks for spending the time to write a review and a thoughtful suggestion!
The GUI has a special peers tab, and the RPC console is only for expert users.
If the peers tab is lacking some feature, it may be better to submit the feature to the gui repo.
I don't mind the changes here, but I am ~0 on them.
The GUI has a special peers tab, and the RPC console is only for expert users.
If the peers tab is lacking some feature, it may be better to submit the feature to the gui repo.
I don't mind the changes here, but I am ~0 on them.
This is what I'm using and maintaining for my own builds. It covers my gui, cli, rpc, tracing uses. I don't intend to write specialized gui coverage or submit this to the gui repo as it's a rpc change.
If you don't use or need any of the features it's fine to be ~0 on them.
Reviewed the changes, and the filter works correctly, returning an empty list if the peer id is not found.
My hesitation is around the precedent. I think this type of filtering makes more sense for larger, unbounded datasets. For getpeerinfo, which returns at most 125 peers, the cost seems small to me.
That said, I am not strongly opposed. Though I’m not sure where the line is. If we add this, the same argument applies to other read RPCs, like listunspent, (filter with txid etc...)
This is useful when using bitcoin-cli or the console to inspect specific peers referenced in debug.log.
Next to the hesitations voiced already, I'm also not sure if this is a strong reason. If you are already combing through the debug log, I think the additional pain of using some kind of piped filter for your data is pretty minimal. I doubt there is a large overlap of users sifting through the logs, but not capable of using a command line tool or pasting the output from the debug console into an llm chat box.
This is useful when using bitcoin-cli or the console to inspect specific peers referenced in debug.log.
Next to the hesitations voiced already, I'm also not sure if this is a strong reason. If you are already combing through the debug log, I think the additional pain of using some kind of piped filter for your data is pretty minimal. I doubt there is a large overlap of users sifting through the logs, but not capable of using a command line tool or pasting the output from the debug console into an llm chat box.
Pretty useful for me, I don't even have jq built in my system. But thanks for the feedback.