Support JSON-RPC 2.0 when requested by client#27101
Support JSON-RPC 2.0 when requested by client#27101ryanofsky merged 9 commits intobitcoin:masterfrom
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
d21b25b to
440d6a2
Compare
|
Concept ACK |
97af16b to
b139110
Compare
|
Concept ACK. Migrating to strict JSON-RPC 2.0 for |
|
Concept ACK. Might be worth updating a few other things at the same time if you continue to move ahead:
|
|
@willcl-ark thanks, I added comments and release notes. I also wrote a tiny testing package using libjson-rpc-cpp to check against an "outside" JSON-RPC 2.0 implementation. The package is https://github.com/pinheadmz/jsonrpc-bitcoin and seems to like the 2.0 implementation so far. |
ec2b51b to
d7a87a1
Compare
|
This PR is ready for code review if any of you fine handsome concept-ACKers have the time ❤️ |
willcl-ark
left a comment
There was a problem hiding this comment.
ACK 2da823199
Left a few comments which could be addressed here, in a followup or not at all. None of them materially affect the current implementation of this feature which works well.
Although, I would be curious to know why we are still responding with http 500 errors for invalid json after the move to json 2.0.
test/functional/interface_rpc.py
Outdated
There was a problem hiding this comment.
Should this not be a status: 200, error: -32602 under json 2.0?
There was a problem hiding this comment.
Hm yeah good point, a few error codes like this are specified. One problem with this is that our application error RPC_INVALID_PARAMETER (-8) is coded ~186 times in RPC functions everywhere. I suppose we could re-map those error codes back to the jsonrpc 2.0 spec in JSONRPCExecOne()? Only when the request indicates jsonrpc 2.0
5388efb to
c9a3862
Compare
|
Added a scripted-diff to completely replace all occurrences of the application-defined |
Change parameters from const references to values, so they can be moved into the reply instead of copied. Also update callers to move instead of copy.
Simplify the request handling flow so that errors and results only come from JSONRPCExec()
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 58cb22c with some reservations.
I would say the current implementation of this looks pretty good, but it is hard to be confident about it due to gaps in test coverage (no coverage for batch request without version numbers, or with unrecognized version numbers, for non-2.0 batch notifications, or for empty requests where a behavior change was reported earlier)
Also most of the test coverage that is added is added after the code changes, so there is not much assurance individual code changes have the expected effects on behavior.
But aside from gaps in tests I didn't see any major issues with the implementation, other than:
- Parsing of request id seemed to have been moved incorrectly, so "JSON-RPC version not supported" exception returns
"id": nullinstead of id of the request - Const references and std::move were not used correctly in a few places so objects are copied instead of moved
- Some things are not well documented
I left some more specific comments below, but to save time, I implemented a branch that has all the improvements I would like to see here: review.27101.5-edit (diff). The main thing the branch does is add more test coverage, and add it before the code changes rather than after, so it possible to actually see the effects code changes have on responses and understand the them better. The branch can be fetched/compared locally with:
git fetch -n https://github.com/ryanofsky/bitcoin review.27101.5-edit:review.27101.5-edit tag review.27101.5-base tag review.27101.5
git diff review.27101.5 review.27101.5-edit
git range-diff review.27101.5-base review.27101.5 review.27101.5-edit
test/functional/interface_rpc.py
Outdated
| ] | ||
| ) | ||
|
|
||
| self.log.info("Testing basic JSON-RPC 1.0 batch request...") |
There was a problem hiding this comment.
In commit "test: cover JSONRPC 2.0 requests, batches, and notifications" (92c6513)
This is confusing because there should be no such thing as a JSON-RPC 1.0 batch request, batches were introduced in 2.0. Also specifying req["jsonrpc"] = "1.0" is nonstandard. Would be good to clarify what behavior is standard and what is just being done for backwards compatibility.
src/rpc/request.cpp
Outdated
| id = request.find_value("id"); | ||
|
|
||
| // Check for JSON-RPC 2.0 (default 1.1) | ||
| // We must do this before looking for the id field |
There was a problem hiding this comment.
In commit "rpc: JSON-RPC 2.0 should not respond to "notifications"" (d62dcf8)
This is just stated without a reason and doesn't appear to be true.
Also, checking for the version field after the id field is bad because it means if a batch request includes an unrecognized version number, the id is lost and there is no indication which call in the batch is causing the problem.
src/httprpc.cpp
Outdated
| static bool g_rpc_whitelist_default = false; | ||
|
|
||
| static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) | ||
| static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const JSONRPCRequest& jreq) |
There was a problem hiding this comment.
In commit "rpc: make error and result mutually exclusive in JSON-RPC 2.0 replies" (9cae84c)
This is not a great commit description because it is only describing one of the 3 changes made in this commit. In addition to making error and result multually exclusive it is also:
- Adding jsonrpc 2.0 field to responses
- Making non-batch jsonrpc 2.0 requests return 200 http status instead of error statuses when the RPC method fails
In the branch I posted #27101 (review) this commit is split into two parts and includes tests to clarify the changes.
src/rpc/request.h
Outdated
| UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); | ||
| UniValue JSONRPCReplyObj(UniValue result, UniValue error, const UniValue& id); | ||
| std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); | ||
| UniValue JSONRPCReplyObj(UniValue result, UniValue error, const UniValue& id, JSONVersion json_version = JSONVersion::JSON_1_BTC); |
There was a problem hiding this comment.
In commit "rpc: make error and result mutually exclusive in JSON-RPC 2.0 replies" (9cae84c)
Would be safest to drop default argument and make it required to specify the version. I think differences between versions are subtle and confusing enough that the version should always be explicit.
There was a problem hiding this comment.
ok thanks, originally I wanted to use a default value to ensure that bitcoin-cli continued to work without any changes
There was a problem hiding this comment.
Added the explicit arguments to the calls in bitcoin-cli.cpp, so far that's the only change i made to your branch
src/rpc/request.h
Outdated
|
|
||
| #include <univalue.h> | ||
|
|
||
| class JSONRPCRequest; |
There was a problem hiding this comment.
In commit "rpc: make error and result mutually exclusive in JSON-RPC 2.0 replies" (9cae84c)
This declaration appears to be unused
There was a problem hiding this comment.
Without it there is a compile error:
In file included from ./rpc/server.h:9:
./rpc/request.h:22:79: error: unknown type name 'JSONRPCRequest'
22 | std::string JSONRPCReply(const UniValue& result, const UniValue& error, const JSONRPCRequest& jreq);
| ^
1 error generated.
src/rpc/request.cpp
Outdated
| } | ||
|
|
||
| UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id) | ||
| UniValue JSONRPCReplyObj(UniValue result, UniValue error, const UniValue& id) |
There was a problem hiding this comment.
In commit "rpc: use move semantics in JSONRPCReplyObj()" (51a071f)
To make sense, this commit needs to use std::move below for result, error, and id. The point of changing these arguments from const to non-const is to allow them to be moved from, and avoid the copies that would otherwise happen below.
src/rpc/server.cpp
Outdated
There was a problem hiding this comment.
In commit "rpc: make error and result mutually exclusive in JSON-RPC 2.0 replies" (9cae84c)
re: #27101 (comment)
my understanding is that the error message will continue to be passed by reference through the end of
JSONRPCReplyObj
It will be passed by value, but it will be copied because std::move is not specified, so this is less than ideal. The fix here is just to drop const from the catch declaration and then add std::move to move the value from the exception to the reply object.
src/httprpc.cpp
Outdated
| all_notifications = false; | ||
| } | ||
| } | ||
| // All-notification batch expects no response |
There was a problem hiding this comment.
In commit "rpc: JSON-RPC 2.0 should not respond to "notifications"" (d62dcf8)
I think this needs some explanation about how valRequest.size() check is here for backward compatibility since it is surprising and violates the 2.0 spec.
|
@ryanofsky thanks so much, again, for your help. Especially in understanding the |
cbergqvist
left a comment
There was a problem hiding this comment.
reACK 8583c2f93d4eb020bf1e3b74860b691ec12e5eee
Ran most functional tests including interface_rpc.py without any failures. Ran make check without failures or skips.
Happy that the version parameters to JSONRPCReplyObj() etc are now explicit.
src/httprpc.cpp
Outdated
There was a problem hiding this comment.
Here the caught UniValue& is not explicitly std::moved. But on line 272 it is. Is that intentional?
There was a problem hiding this comment.
Good catch thanks. I'll need @ryanofsky to weigh in here because I keep misunderstanding c++ move semantics. But here's what I think is happening:
In JSONRPCReplyObj() in request.cpp the really import move-instead-of-copy is when we call reply.pushKV(...): https://github.com/pinheadmz/bitcoin/blob/8583c2f93d4eb020bf1e3b74860b691ec12e5eee/src/rpc/request.cpp#L64
That makes me think that the std::move() currently in JSONErrorReply() in httprpc.cpp is unnecessary: https://github.com/esotericnonsense/bitcoin/blob/8583c2f93d4eb020bf1e3b74860b691ec12e5eee/src/httprpc.cpp#L90
...and also unnecessary is the one you see on line 272.
So if I'm finally understanding, which is unlikely, then there are two places where std::move can be removed but actually this line 244 right here is OK, because the import move-instead-of-copy happens inside the function being called.
There was a problem hiding this comment.
re: #27101 (comment)
I think @cbergqvist is right here and e should be replaced by std::move(e) on line 244.
The JSONRPCReplyObj signature is:
UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONVersion json_version);So without std::move(e), the error parameter will be initialized by making a copy of e instead of moving from e. This is because e is an lvalue, while std::move(e) is an rvalue.
In
JSONRPCReplyObj()inrequest.cppthe really import move-instead-of-copy is when we callreply.pushKV(...): https://github.com/pinheadmz/bitcoin/blob/8583c2f93d4eb020bf1e3b74860b691ec12e5eee/src/rpc/request.cpp#L64
That is true. The signature for pushKV is void pushKV(std::string key, UniValue val), so without std::move(error), on that line the function parameter val would be initialized by copying not moving.
That makes me think that the
std::move()currently inJSONErrorReply()inhttprpc.cppis unnecessary: https://github.com/esotericnonsense/bitcoin/blob/8583c2f93d4eb020bf1e3b74860b691ec12e5eee/src/httprpc.cpp#L90
This is not true. std::move is needed to avoid copy-initializing the JSONRPCReplyObj error parameter there, just like on line 272 and on line
So if I'm finally understanding, which is unlikely, then there are two places where
std::movecan be removed but actually this line 244 right here is OK, because the import move-instead-of-copy happens inside the function being called.
Yeah this is a little off. What you are saying is true if the function signature accepts parameters by reference. For example if you have function that looks like:
void MyFunction(SomeType& param);
void MyFunction(const SomeType& param);
void MyFunction(SomeType&& param);In this case, no matter whether you call MyFunction with std::move or without it, the parameter param is going to be a reference not a value, so no copy will be created just by calling the function. There could copies of param made inside the function, depending on what it does internally, as you say. But merely calling the function will not copy the parameter.
However, if you have a function that takes a non-reference value parameter like:
void MyFunction(SomeType param);No matter what the function does internally, just calling the function is going to copy-construct param variable or move-construct it. And unless you use std::move or the argument is already an rvalue, the argument will be copied.
There was a problem hiding this comment.
Had to check whether UniValue even had a move-constructor. It seems like one should be generated implicitly if my readings of https://en.cppreference.com/w/cpp/language/move_constructor#Implicitly-declared_move_constructor and univalue.h are correct.
@ryanofsky's explanation rings true with my long underutilized C++11 neurons.
src/httprpc.cpp
Outdated
There was a problem hiding this comment.
Might be more correct & compatible to append "\r\n". See https://stackoverflow.com/questions/5757290/http-header-line-break-style
There was a problem hiding this comment.
That's a reasonable comment but I kept the single character which is how it worked on master (see below). I see a few examples of \r\n in the codebase but its rare and I'm not sure how it's decided.
Lines 52 to 56 in 63d0b93
There was a problem hiding this comment.
In commit "rpc: refactor single/batch requests" (a64a2b7)
re: #27101 (comment)
HTTP headers use \r\n, but this line is generating the JSON object in the body of the response, after the headers. And the JSON could be written with either \r\n or \n or no trailing line break at all. I think it would probably make sense not to include any trailing line break, but existing code uses \n, so in a refactoring commit it seems best not to change this.
ryanofsky
left a comment
There was a problem hiding this comment.
Code review ACK 8583c2f93d4eb020bf1e3b74860b691ec12e5eee. Looks very good! I left several comments, but none are critical and they could be followed up later.
src/httprpc.cpp
Outdated
There was a problem hiding this comment.
In commit "rpc: refactor single/batch requests" (a64a2b7)
re: #27101 (comment)
HTTP headers use \r\n, but this line is generating the JSON object in the body of the response, after the headers. And the JSON could be written with either \r\n or \n or no trailing line break at all. I think it would probably make sense not to include any trailing line break, but existing code uses \n, so in a refactoring commit it seems best not to change this.
src/rpc/request.cpp
Outdated
| if (!valJsonRPC.isStr()) { | ||
| throw JSONRPCError(RPC_INVALID_REQUEST, "jsonrpc field must be a string"); | ||
| } | ||
| if (valJsonRPC.get_str() == "1.0") { |
There was a problem hiding this comment.
In commit "rpc: identify JSON-RPC 2.0 requests" (6d0be75)
Might be good to add a comment noting that the "jsonrpc" version field was added in the JSON-RPC 2.0 spec, so value "1.0" is nonstandard. But it is accepted for backwards compatibility, because some old documentation (incorrectly) suggested adding it.
src/rpc/request.cpp
Outdated
|
|
||
| // Check for JSON-RPC 2.0 (default 1.1) | ||
| m_json_version = JSONVersion::JSON_1_BTC; | ||
| const UniValue& valJsonRPC = request.find_value("jsonrpc"); |
There was a problem hiding this comment.
In commit "rpc: identify JSON-RPC 2.0 requests" (6d0be75)
Variable name valJsonRPC doesn't really follow the suggested coding style which calls for snake_case. Could rename it to "jsonrpc_version"
src/rpc/request.h
Outdated
|
|
||
| #include <univalue.h> | ||
|
|
||
| enum class JSONVersion { |
There was a problem hiding this comment.
In commit "rpc: identify JSON-RPC 2.0 requests" (6d0be75)
Technically JSON-RPC version would be more accurate the JSON version. could s/JSON/JSONRPC/ throughout this enum
There was a problem hiding this comment.
Is JSON/JSONRPC really necessary for the values themselves if the enum is of the type enum class so that one always has to prepend the type? - JSONRPCVersion::JSONRPC_1_BTC vs JSONRPCVersion::1_BTC.
There was a problem hiding this comment.
re: #27101 (comment)
Is JSON/JSONRPC really necessary for the values themselves if the
enumis of the typeenum classso that one always has to prepend the type? -JSONRPCVersion::JSONRPC_1_BTCvsJSONRPCVersion::1_BTC.
Good point. I'd probably call the constants V1_LEGACY and V2 if renaming.
src/rpc/server.cpp
Outdated
There was a problem hiding this comment.
In commit "rpc: Avoid returning HTTP errors for JSON-RPC 2.0 requests" (56b6c33646f398e1b2efab64cbaf7ed86e3bbd06)
This is probably supposed to say 1.1 not 1.2
src/rpc/server.cpp
Outdated
There was a problem hiding this comment.
In commit "rpc: Avoid returning HTTP errors for JSON-RPC 2.0 requests" (56b6c33646f398e1b2efab64cbaf7ed86e3bbd06)
Returning RPC_PARSE_ERROR does not seem accurate if the request was parsed successfully but there was an error executing it. This was also a preexisting problem in HTTPReq_JSONRPC for batch requests, but now the behavior will happen for all version 2 single requests as well. Would suggest cleaning this up with a change like:
--- a/src/httprpc.cpp
+++ b/src/httprpc.cpp
@@ -204,7 +204,12 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
return false;
}
- reply = JSONRPCExec(jreq);
+ // Legacy 1.0/1.1 behavior is for failed requests to throw
+ // exceptions which return HTTP errors and RPC errors to the client.
+ // 2.0 behavior is to catch exceptions and return HTTP success with
+ // RPC errors, as long as there is not HTTP server error.
+ const bool catch_errors{jreq.m_json_version == JSONVersion::JSON_2_0};
+ reply = JSONRPCExec(jreq, catch_errors);
// array of requests
} else if (valRequest.isArray()) {
@@ -233,12 +238,12 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
// in "HTTP OK" responses.
try {
jreq.parse(valRequest[i]);
- reply.push_back(JSONRPCExec(jreq));
} catch (UniValue& e) {
reply.push_back(JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version));
} catch (const std::exception& e) {
reply.push_back(JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version));
}
+ reply.push_back(JSONRPCExec(jreq, /*catch_errors=*/true));
}
}
else
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -358,22 +358,18 @@ bool IsDeprecatedRPCEnabled(const std::string& method)
return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end();
}
-UniValue JSONRPCExec(const JSONRPCRequest& jreq)
+UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors)
{
UniValue result;
- if (jreq.m_json_version == JSONVersion::JSON_2_0) {
- // JSONRPC 2.0 behavior: only throw HTTP error if the server is actually
- // broken. Otherwise errors are sent back in "HTTP OK" responses.
+ if (catch_errors) {
try {
result = tableRPC.execute(jreq);
} catch (UniValue& e) {
return JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version);
} catch (const std::exception& e) {
- return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version);
+ return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_MISC_ERROR, e.what()), jreq.id, jreq.m_json_version);
}
} else {
- // Legacy Bitcoin JSONRPC 1.0/1.2 behavior:
- // Single requests may throw HTTP errors, handled by caller or client
result = tableRPC.execute(jreq);
}
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -181,7 +181,7 @@ extern CRPCTable tableRPC;
void StartRPC();
void InterruptRPC();
void StopRPC();
-UniValue JSONRPCExec(const JSONRPCRequest& jreq);
+UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors);
// Drop witness when serializing for RPC?
bool RPCSerializationWithoutWitness();There was a problem hiding this comment.
Thanks so much for the patch. I had to move this line back up to where it was, otherwise the test fails because after pushing the error into the batch reply, we would also then push a weird extra junk reply with the current id but the result from the last successful query
diff --git a/src/httprpc.cpp b/src/httprpc.cpp
index 0e3e66c515..777ad32bbe 100644
--- a/src/httprpc.cpp
+++ b/src/httprpc.cpp
@@ -238,12 +238,12 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
// in "HTTP OK" responses.
try {
jreq.parse(valRequest[i]);
+ reply.push_back(JSONRPCExec(jreq, /*catch_errors=*/true));
} catch (UniValue& e) {
reply.push_back(JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version));
} catch (const std::exception& e) {
reply.push_back(JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version));
}
- reply.push_back(JSONRPCExec(jreq, /*catch_errors=*/true));
}
}
elseThere was a problem hiding this comment.
I'm trying to figure out how to write a test to catch one of these RPC_MISC_ERROR since they obviously weren't catching a RPC_PARSE_ERROR before adding your patch to the commit. If I understand correctly, that "misc" error would only throw if there was a bug in an RPC command because most of our try blocks try to catch a UniValue error first with a specific code.
Only for JSON-RPC 2.0 requests.
Avoid returning HTTP status errors for non-batch JSON-RPC 2.0 requests if the RPC method failed but the HTTP request was otherwise valid. Batch requests already did not return HTTP errors previously.
For JSON-RPC 2.0 requests we need to distinguish between a missing "id" field and "id":null. This is accomplished by making the JSONRPCRequest id property a std::optional<UniValue> with a default value of UniValue::VNULL. A side-effect of this change for non-2.0 requests is that request which do not specify an "id" field will no longer return "id": null in the response.
|
force push to cbc6c44:
thanks again for the reviews @cbergqvist @ryanofsky |
| static bool g_rpc_whitelist_default = false; | ||
|
|
||
| static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) | ||
| static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCRequest& jreq) |
There was a problem hiding this comment.
nit: JSONErrorReply -> JSONRPCErrorReply, although it could be argued that it actually does write a JSON object in the response.
cbergqvist
left a comment
There was a problem hiding this comment.
re ACK cbc6c44
All functional tests passed (with a few automatic skips), except feature_dbcrash - slow, unrelated => excluded, and feature_index_prune => timed out because rebase with bumped timeout has been held-off.
| { | ||
| rpc_result = JSONRPCReplyObj(NullUniValue, | ||
| JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | ||
| UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors) |
There was a problem hiding this comment.
Would have gone the opposite way and called it throw_errors since it is an.. exception.. to maintain legacy behavior. Sorry for not catching that earlier.
There was a problem hiding this comment.
re: #27101 (comment)
Would have gone the opposite way and called it
throw_errorssince it is an.. exception.. to maintain legacy behavior. Sorry for not catching that earlier.
I think either way is fine, but catch_errors does seem more literally correct since the argument is just controlling whether the exceptions will be caught. Errors will be still be thrown regardless.
| } | ||
| // The "jsonrpc" key was added in the 2.0 spec, but some older documentation | ||
| // incorrectly included {"jsonrpc":"1.0"} in a request object, so we | ||
| // maintain that for backwards compatibility. |
There was a problem hiding this comment.
In commit "rpc: identify JSON-RPC 2.0 requests" (2ca1460)
I think it would be a little clearer to say "continue to accept that" instead of "maintain that." Otherwise it sounds like we are trying to maintain incorrectly including the field, not just allowing it if it is specified.
| { | ||
| rpc_result = JSONRPCReplyObj(NullUniValue, | ||
| JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); | ||
| UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors) |
There was a problem hiding this comment.
re: #27101 (comment)
Would have gone the opposite way and called it
throw_errorssince it is an.. exception.. to maintain legacy behavior. Sorry for not catching that earlier.
I think either way is fine, but catch_errors does seem more literally correct since the argument is just controlling whether the exceptions will be caught. Errors will be still be thrown regardless.
|
@willcl-ark, @tdb3, any interest in re-acking? This seems like it could definitely be merged with another ack |
Definitely. I'll plan to take a look tonight. |
tdb3
left a comment
There was a problem hiding this comment.
re ACK for cbc6c44
Great work. Performed brief code review. Re-ran the tests described in #27101 (comment) again (actions 1 through 7, with the caveat that v27.0 was used as the baseline for comparison rather v26.0, and regtest was used). Everything worked as expected. Also ran all functional tests (passed).
| and responds accordingly. A 2.0 request is identified by the presence of | ||
| `"jsonrpc": "2.0"` in the request body. If that key + value is not present in a request, | ||
| the legacy JSON-RPC v1.1 protocol is followed instead, which was the only available | ||
| protocol in previous releases. |
There was a problem hiding this comment.
nit: Now that this is merged, it could say "in 27.0 and prior releases." Otherwise, on 29.x it will read as-if 28.0 had it missing.
|
|
||
| - Returning HTTP "204 No Content" responses to JSON-RPC 2.0 notifications instead of full responses. | ||
| - Returning HTTP "200 OK" responses in all other cases, rather than 404 responses for unknown methods, 500 responses for invalid parameters, etc. | ||
| - Returning either "result" fields or "error" fields in JSON-RPC responses, rather than returning both fields with one field set to null. |
There was a problem hiding this comment.
nit instead of duplicating the section from doc/JSON-RPC-interface.md here, it seems better to link/refer to it. Otherwise, if the section is updated, this may go stale or must be updated at the same time.
| except JSONRPCException as exc: | ||
| assert_equal(exc.error["code"], expected_rpc_code) | ||
| assert_equal(exc.http_status, expected_http_status) | ||
| RPC_INVALID_ADDRESS_OR_KEY = -5 |
There was a problem hiding this comment.
@pinheadmz did you end up following up to these comments?
| try { | ||
| jreq.parse(valRequest[i]); | ||
| response = JSONRPCExec(jreq, /*catch_errors=*/true); | ||
| } catch (UniValue& e) { | ||
| response = JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version); | ||
| } catch (const std::exception& e) { | ||
| response = JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version); | ||
| } |
There was a problem hiding this comment.
(Came across this block again while working on something else. It struck me that the exception handling looked redundant as we catch the same exact exception types inside JSONRPCExec(jreq, /*catch_errors=*/true). But the jreq.parse() call will throw on missing methods etc.
One could move out response = JSONRPCExec(jreq, /*catch_errors=*/true); to after the block, but then one would need to guard against using a jreq that failed to parse. So the current version of the block is probably preferable both in readability and efficiency).
Closes #2960
Bitcoin Core's JSONRPC server behaves with a special blend of 1.0, 1.1 and 2.0 behaviors. This introduces compliance issues with more strict clients. There are the major misbehaviors that I found:
"error"and"result"fields together in a response object.#15495 added regression tests after a discussion in #15381 to kinda lock in our RPC behavior to preserve backwards compatibility.
#12435 was an attempt to allow strict 2.0 compliance behind a flag, but was abandoned.
The approach in this PR is not strict and preserves backwards compatibility in a familiar bitcoin-y way: all old behavior is preserved, but new rules are applied to clients that opt in. One of the rules in the JSON RPC 2.0 spec is that the kv pair
"jsonrpc": "2.0"must be present in the request. Well, let's just use that to trigger strict 2.0 behavior! When that kv pair is included in a request object, the response will adhere to strict JSON-RPC 2.0 rules, essentially:"error"OR"result"but never bothIf this is merged next steps can be:
If we can one day remove the old 1.0/1.1 behavior we can clean up the rpc code quite a bit.