Merge bitcoin/bitcoin#35868: rpc, wallet: fix invalid JSON in HelpExampleRpc curl examples
What changed, and why it matters
This commit fixes broken copy-paste examples in Bitcoin Core's command-line help text. The examples showed curl commands with JSON payloads that were not valid JSON, so users who copied them directly would get a JSON parse error. It is a documentation and developer-experience bug, not a security vulnerability, and it does not affect live code handling real transactions or network traffic.
No security action required. Treat as a normal documentation/test fix. If backporting, include only for user-experience consistency; it does not close any attack surface.
Security signals we found
No security relevance claimed by vendor
Only documentation strings and a functional test changed
No input parsing, authentication, or consensus code modified
No memory safety, cryptography, or privilege-boundary changes
Evidence from the diff
The patch corrects HelpExampleRpc call sites across RPC and wallet code so that the generated curl examples contain valid JSON in the –data-binary payload. It also adds a regression test in test/functional/rpc_help.py::dump_help() that extracts every curl payload and asserts json.loads() succeeds. The changes are purely in help-text generation and a functional test; no RPC parsing, consensus, wallet, or networking logic is modified.
Changed components
RPC help text generation (src/rpc/blockchain.cpp, mempool.cpp, net.cpp, node.cpp)Wallet RPC help text generation (src/wallet/rpc/addresses.cpp, backup.cpp, coins.cpp, wallet.cpp)Functional test rpc_help.pyInspect captured patch +26 / −17
### src/rpc/blockchain.cpp
@@ -540,7 +540,7 @@ static RPCMethod getblockfrompeer()
RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
RPCExamples{
HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
- + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ + HelpExampleRpc("getblockfrompeer", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", 0)")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
### src/rpc/mempool.cpp
@@ -872,7 +872,7 @@ static RPCMethod getmempoolcluster()
RPCResult::Type::OBJ, "", "", ClusterDescription()},
RPCExamples{
HelpExampleCli("getmempoolcluster", "txid")
- + HelpExampleRpc("getmempoolcluster", "txid")
+ + HelpExampleRpc("getmempoolcluster", R"("txid")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -1169,7 +1169,7 @@ static RPCMethod importmempool()
RPCArgOptions{.oneline_description = "options"}},
},
RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}},
- RPCExamples{HelpExampleCli("importmempool", "/path/to/mempool.dat") + HelpExampleRpc("importmempool", "/path/to/mempool.dat")},
+ RPCExamples{HelpExampleCli("importmempool", "/path/to/mempool.dat") + HelpExampleRpc("importmempool", R"("/path/to/mempool.dat")")},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue {
const NodeContext& node{EnsureAnyNodeContext(request.context)};
### src/rpc/net.cpp
@@ -335,7 +335,7 @@ static RPCMethod addnode()
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\" true")
- + HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\" true")
+ + HelpExampleRpc("addnode", R"("192.168.0.6:8333", "onetry", true)")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -410,7 +410,7 @@ static RPCMethod addconnection()
}},
RPCExamples{
HelpExampleCli("addconnection", "\"192.168.0.6:8333\" \"outbound-full-relay\" true")
- + HelpExampleRpc("addconnection", "\"192.168.0.6:8333\" \"outbound-full-relay\" true")
+ + HelpExampleRpc("addconnection", R"("192.168.0.6:8333", "outbound-full-relay", true)")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -1083,7 +1083,7 @@ static RPCMethod sendmsgtopeer()
},
RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}},
RPCExamples{
- HelpExampleCli("sendmsgtopeer", "0 \"addr\" \"ffffff\"") + HelpExampleRpc("sendmsgtopeer", "0 \"addr\" \"ffffff\"")},
+ HelpExampleCli("sendmsgtopeer", "0 \"addr\" \"ffffff\"") + HelpExampleRpc("sendmsgtopeer", R"(0, "addr", "ffffff")")},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue {
const NodeId peer_id{request.params[0].getInt<int64_t>()};
const auto msg_type{self.Arg<std::string_view>("msg_type")};
### src/rpc/node.cpp
@@ -378,7 +378,7 @@ static RPCMethod getindexinfo()
HelpExampleCli("getindexinfo", "")
+ HelpExampleRpc("getindexinfo", "")
+ HelpExampleCli("getindexinfo", "txindex")
- + HelpExampleRpc("getindexinfo", "txindex")
+ + HelpExampleRpc("getindexinfo", R"("txindex")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
### src/wallet/rpc/addresses.cpp
@@ -642,7 +642,7 @@ RPCMethod listlabels()
"\nList labels that have sending addresses\n"
+ HelpExampleCli("listlabels", "send") +
"\nAs a JSON-RPC call\n"
- + HelpExampleRpc("listlabels", "receive")
+ + HelpExampleRpc("listlabels", R"("receive")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
### src/wallet/rpc/backup.cpp
@@ -618,9 +618,9 @@ RPCMethod restorewallet()
},
RPCExamples{
HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
- + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
- + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
- + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
+ + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
+ + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
+ + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
### src/wallet/rpc/coins.cpp
@@ -514,7 +514,7 @@ RPCMethod listunspent()
RPCExamples{
HelpExampleCli("listunspent", "")
+ HelpExampleCli("listunspent", "6 9999999 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
- + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
+ + HelpExampleRpc("listunspent", strprintf(R"(6, 9999999, ["%s","%s"])", EXAMPLE_ADDRESS[0], EXAMPLE_ADDRESS[1]))
+ HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'")
+ HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")
},
### src/wallet/rpc/wallet.cpp
@@ -249,7 +249,7 @@ static RPCMethod loadwallet()
+ HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"")
+ "\nLoad wallet using absolute path (Windows):\n"
+ HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
- + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"")
+ + HelpExampleRpc("loadwallet", R"("DriveLetter:\\path\\to\\walletname")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -457,7 +457,7 @@ static RPCMethod unloadwallet()
}},
RPCExamples{
HelpExampleCli("unloadwallet", "wallet_name")
- + HelpExampleRpc("unloadwallet", "wallet_name")
+ + HelpExampleRpc("unloadwallet", R"("wallet_name")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -761,7 +761,7 @@ static RPCMethod createwalletdescriptor()
},
RPCExamples{
HelpExampleCli("createwalletdescriptor", "bech32m")
- + HelpExampleRpc("createwalletdescriptor", "bech32m")
+ + HelpExampleRpc("createwalletdescriptor", R"("bech32m")")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -851,7 +851,7 @@ RPCMethod addhdkey()
},
},
RPCExamples{
- HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
+ HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", R"("xprv")")
},
[&](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
### test/functional/rpc_help.py
@@ -8,6 +8,7 @@
from test_framework.util import assert_equal, assert_raises_rpc_error
from collections import defaultdict
+import json
import os
import re
@@ -153,9 +154,17 @@ def dump_help(self):
os.mkdir(dump_dir)
calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')]
for call in calls:
+ help_text = self.nodes[0].help(call)
with open(os.path.join(dump_dir, call), 'w') as f:
# Make sure the node can generate the help at runtime without crashing
- f.write(self.nodes[0].help(call))
+ f.write(help_text)
+ # Make sure any curl examples have a JSON-RPC payload that is valid JSON
+ for match in re.finditer(r"--data-binary '(.*)' -H", help_text):
+ payload = match.group(1)
+ try:
+ json.loads(payload)
+ except json.JSONDecodeError as e:
+ raise AssertionError(f"HelpExampleRpc for '{call}' is not valid JSON: {payload!r}\n{e}")
def wallet_help(self):
assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.