Skip to content

Commit a44b0f7

Browse files
committed
Merge bitcoin/bitcoin#30238: json-rpc 2.0 followups: docs, tests, cli
1f6ab12 minor: remove unnecessary semicolons from RPC content type examples (Matthew Zipkin) b225295 test: use json-rpc 2.0 in all functional tests by default (Matthew Zipkin) 391843b bitcoin-cli: use json-rpc 2.0 (Matthew Zipkin) d39bdf3 test: remove unused variable in interface_rpc.py (Matthew Zipkin) 0ead71d doc: update and link for JSON-RPC 2.0 (Matthew Zipkin) Pull request description: This is a follow-up to #27101. - Addresses [post-merge comments ](bitcoin/bitcoin#27101 (comment)) - bitcoin-cli uses JSON-RPC 2.0 - functional tests use JSON-RPC 2.0 by default (exceptions are in the regression tests added by #27101) ACKs for top commit: tdb3: ACK 1f6ab12 cbergqvist: ACK 1f6ab12 Tree-SHA512: 49bf14c70464081280216ece538a2f5ec810bac80a86a83ad3284f0f1b017edf755a1a74a45be279effe00218170cafde7c2de58aed07097a95c2c6b837a6b6c
2 parents 2ad6e8e + 1f6ab12 commit a44b0f7

File tree

9 files changed

+41
-30
lines changed

9 files changed

+41
-30
lines changed

doc/JSON-RPC-interface.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ requests when multiple wallets are in use.
3333

3434
```sh
3535
# Get block count from the / endpoint when rpcuser=alice and rpcport=38332
36-
$ curl --user alice --data-binary '{"jsonrpc": "1.0", "id": "0", "method": "getblockcount", "params": []}' -H 'content-type: application/json;' localhost:38332/
36+
$ curl --user alice --data-binary '{"jsonrpc": "2.0", "id": "0", "method": "getblockcount", "params": []}' -H 'content-type: application/json' localhost:38332/
3737

3838
# Get balance from the /wallet/walletname endpoint when rpcuser=alice, rpcport=38332 and rpcwallet=desc-wallet
39-
$ curl --user alice --data-binary '{"jsonrpc": "1.0", "id": "0", "method": "getbalance", "params": []}' -H 'content-type: application/json;' localhost:38332/wallet/desc-wallet
39+
$ curl --user alice --data-binary '{"jsonrpc": "2.0", "id": "0", "method": "getbalance", "params": []}' -H 'content-type: application/json' localhost:38332/wallet/desc-wallet
4040

4141
```
4242

@@ -80,15 +80,15 @@ The server recognizes [JSON-RPC v2.0](https://www.jsonrpc.org/specification) req
8080
and responds accordingly. A 2.0 request is identified by the presence of
8181
`"jsonrpc": "2.0"` in the request body. If that key + value is not present in a request,
8282
the legacy JSON-RPC v1.1 protocol is followed instead, which was the only available
83-
protocol in previous releases.
83+
protocol in v27.0 and prior releases.
8484

8585
|| 1.1 | 2.0 |
8686
|-|-|-|
8787
| Request marker | `"version": "1.1"` (or none) | `"jsonrpc": "2.0"` |
8888
| Response marker | (none) | `"jsonrpc": "2.0"` |
8989
| `"error"` and `"result"` fields in response | both present | only one is present |
9090
| HTTP codes in response | `200` unless there is any kind of RPC error (invalid parameters, method not found, etc) | Always `200` unless there is an actual HTTP server error (request parsing error, endpoint not found, etc) |
91-
| Notifications: requests that get no reply | (not supported) | Supported for requests that exclude the "id" field |
91+
| Notifications: requests that get no reply | (not supported) | Supported for requests that exclude the "id" field. Returns HTTP status `204` "No Content" |
9292

9393
## Security
9494

doc/release-notes-27101.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,5 @@ JSON-RPC
22
--------
33

44
The JSON-RPC server now recognizes JSON-RPC 2.0 requests and responds with
5-
strict adherence to the specification (https://www.jsonrpc.org/specification):
6-
7-
- Returning HTTP "204 No Content" responses to JSON-RPC 2.0 notifications instead of full responses.
8-
- Returning HTTP "200 OK" responses in all other cases, rather than 404 responses for unknown methods, 500 responses for invalid parameters, etc.
9-
- Returning either "result" fields or "error" fields in JSON-RPC responses, rather than returning both fields with one field set to null.
5+
strict adherence to the [specification](https://www.jsonrpc.org/specification).
6+
See [JSON-RPC-interface.md](/doc/JSON-RPC-interface.md#json-rpc-11-vs-20) for details.

src/bitcoin-cli.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class AddrinfoRequestHandler : public BaseRequestHandler
298298
}
299299
addresses.pushKV("total", total);
300300
result.pushKV("addresses_known", std::move(addresses));
301-
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
301+
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
302302
}
303303
};
304304

@@ -367,7 +367,7 @@ class GetinfoRequestHandler: public BaseRequestHandler
367367
}
368368
result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
369369
result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
370-
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
370+
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
371371
}
372372
};
373373

@@ -622,7 +622,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
622622
}
623623
}
624624

625-
return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
625+
return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V2);
626626
}
627627

628628
const std::string m_help_doc{
@@ -709,7 +709,7 @@ class GenerateToAddressRequestHandler : public BaseRequestHandler
709709
UniValue result(UniValue::VOBJ);
710710
result.pushKV("address", address_str);
711711
result.pushKV("blocks", reply.get_obj()["result"]);
712-
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY);
712+
return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);
713713
}
714714
protected:
715715
std::string address_str;

src/rpc/request.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params,
4545
request.pushKV("method", strMethod);
4646
request.pushKV("params", params);
4747
request.pushKV("id", id);
48+
request.pushKV("jsonrpc", "2.0");
4849
return request;
4950
}
5051

src/rpc/request.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ enum class JSONRPCVersion {
1717
V2
1818
};
1919

20+
/** JSON-RPC 2.0 request, only used in bitcoin-cli **/
2021
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
2122
UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version);
2223
UniValue JSONRPCError(int code, const std::string& message);

src/rpc/util.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList&
176176
std::string HelpExampleRpc(const std::string& methodname, const std::string& args)
177177
{
178178
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", "
179-
"\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n";
179+
"\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: application/json' http://127.0.0.1:8332/\n";
180180
}
181181

182182
std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args)
@@ -187,7 +187,7 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList&
187187
}
188188

189189
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", "
190-
"\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n";
190+
"\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: application/json' http://127.0.0.1:8332/\n";
191191
}
192192

193193
// Converts a hex string to a public key if possible

src/test/rpc_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ BOOST_AUTO_TEST_CASE(help_example)
552552
// test different argument types
553553
const RPCArgList& args = {{"foo", "bar"}, {"b", true}, {"n", 1}};
554554
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", args), "> bitcoin-cli -named test foo=bar b=true n=1\n");
555-
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
555+
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
556556

557557
// test shell escape
558558
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b'ar"}}), "> bitcoin-cli -named test foo='b'''ar'\n");
@@ -565,15 +565,15 @@ BOOST_AUTO_TEST_CASE(help_example)
565565
obj_value.pushKV("b", false);
566566
obj_value.pushKV("n", 1);
567567
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", obj_value}}), "> bitcoin-cli -named test name='{\"foo\":\"bar\",\"b\":false,\"n\":1}'\n");
568-
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
568+
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
569569

570570
// test array params
571571
UniValue arr_value(UniValue::VARR);
572572
arr_value.push_back("bar");
573573
arr_value.push_back(false);
574574
arr_value.push_back(1);
575575
BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", arr_value}}), "> bitcoin-cli -named test name='[\"bar\",false,1]'\n");
576-
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: application/json;' http://127.0.0.1:8332/\n");
576+
BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: application/json' http://127.0.0.1:8332/\n");
577577

578578
// test types don't matter for shell
579579
BOOST_CHECK_EQUAL(HelpExampleCliNamed("foo", {{"arg", true}}), HelpExampleCliNamed("foo", {{"arg", "true"}}));

test/functional/interface_rpc.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import subprocess
1515

1616

17-
RPC_INVALID_ADDRESS_OR_KEY = -5
1817
RPC_INVALID_PARAMETER = -8
1918
RPC_METHOD_NOT_FOUND = -32601
2019
RPC_INVALID_REQUEST = -32600

test/functional/test_framework/authproxy.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
2727
- HTTP connections persist for the life of the AuthServiceProxy object
2828
(if server supports HTTP/1.1)
29-
- sends protocol 'version', per JSON-RPC 1.1
29+
- sends "jsonrpc":"2.0", per JSON-RPC 2.0
3030
- sends proper, incrementing 'id'
3131
- sends Basic HTTP authentication headers
3232
- parses all JSON numbers that look like floats as Decimal
@@ -117,23 +117,36 @@ def get_request(self, *args, **argsn):
117117
params = dict(args=args, **argsn)
118118
else:
119119
params = args or argsn
120-
return {'version': '1.1',
120+
return {'jsonrpc': '2.0',
121121
'method': self._service_name,
122122
'params': params,
123123
'id': AuthServiceProxy.__id_count}
124124

125125
def __call__(self, *args, **argsn):
126126
postdata = json.dumps(self.get_request(*args, **argsn), default=serialization_fallback, ensure_ascii=self.ensure_ascii)
127127
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
128-
if response['error'] is not None:
129-
raise JSONRPCException(response['error'], status)
130-
elif 'result' not in response:
131-
raise JSONRPCException({
132-
'code': -343, 'message': 'missing JSON-RPC result'}, status)
133-
elif status != HTTPStatus.OK:
134-
raise JSONRPCException({
135-
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
128+
# For backwards compatibility tests, accept JSON RPC 1.1 responses
129+
if 'jsonrpc' not in response:
130+
if response['error'] is not None:
131+
raise JSONRPCException(response['error'], status)
132+
elif 'result' not in response:
133+
raise JSONRPCException({
134+
'code': -343, 'message': 'missing JSON-RPC result'}, status)
135+
elif status != HTTPStatus.OK:
136+
raise JSONRPCException({
137+
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
138+
else:
139+
return response['result']
136140
else:
141+
assert response['jsonrpc'] == '2.0'
142+
if status != HTTPStatus.OK:
143+
raise JSONRPCException({
144+
'code': -342, 'message': 'non-200 HTTP status code'}, status)
145+
if 'error' in response:
146+
raise JSONRPCException(response['error'], status)
147+
elif 'result' not in response:
148+
raise JSONRPCException({
149+
'code': -343, 'message': 'missing JSON-RPC 2.0 result and error'}, status)
137150
return response['result']
138151

139152
def batch(self, rpc_call_list):
@@ -142,7 +155,7 @@ def batch(self, rpc_call_list):
142155
response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
143156
if status != HTTPStatus.OK:
144157
raise JSONRPCException({
145-
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
158+
'code': -342, 'message': 'non-200 HTTP status code'}, status)
146159
return response
147160

148161
def _get_response(self):

0 commit comments

Comments
 (0)