-
Notifications
You must be signed in to change notification settings - Fork 35.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support JSON-RPC 2.0 when requested by client #27101
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
This PR is ready for code review if any of you fine handsome concept-ACKers have the time ❤️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 2da8231
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
AuthServiceProxy.jsonrpc20 = True | ||
# All 200! | ||
expect_http_status(200, -32601, self.nodes[0].invalidmethod) # RPC_METHOD_NOT_FOUND | ||
expect_http_status(200, -8, self.nodes[0].getblockhash, 42) # RPC_INVALID_PARAMETER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this not be a status: 200, error: -32602
under json 2.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
@ryanofsky thanks so much, again, for your help. Especially in understanding the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reACK 8583c2f
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
jreq.parse(valRequest[i]); | ||
response = JSONRPCExec(jreq); | ||
} catch (UniValue& e) { | ||
response = JSONRPCReplyObj(NullUniValue, e, jreq.id, jreq.m_json_version); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the caught UniValue&
is not explicitly std::move
d. But on line 272 it is. Is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.cpp
the 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.cpp
is 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::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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
req->WriteReply(HTTP_OK, strReply); | ||
} catch (const UniValue& objError) { | ||
JSONErrorReply(req, objError, jreq.id); | ||
req->WriteReply(HTTP_OK, reply.write() + "\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id) | |
{ | |
UniValue reply = JSONRPCReplyObj(result, error, id); | |
return reply.write() + "\n"; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK 8583c2f. Looks very good! I left several comments, but none are critical and they could be followed up later.
req->WriteReply(HTTP_OK, strReply); | ||
} catch (const UniValue& objError) { | ||
JSONErrorReply(req, objError, jreq.id); | ||
req->WriteReply(HTTP_OK, reply.write() + "\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -167,6 +167,22 @@ void JSONRPCRequest::parse(const UniValue& valRequest) | |||
// Parse id now so errors from here on will have the id | |||
id = request.find_value("id"); | |||
|
|||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -11,6 +11,11 @@ | |||
|
|||
#include <univalue.h> | |||
|
|||
enum class JSONVersion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #27101 (comment)
Is JSON/JSONRPC really necessary for the values themselves if the
enum
is of the typeenum class
so that one always has to prepend the type? -JSONRPCVersion::JSONRPC_1_BTC
vsJSONRPCVersion::1_BTC
.
Good point. I'd probably call the constants V1_LEGACY
and V2
if renaming.
src/rpc/server.cpp
Outdated
return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version); | ||
} | ||
} else { | ||
// Legacy Bitcoin JSONRPC 1.0/1.2 behavior: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "rpc: Avoid returning HTTP errors for JSON-RPC 2.0 requests" (56b6c33)
This is probably supposed to say 1.1 not 1.2
src/rpc/server.cpp
Outdated
} 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "rpc: Avoid returning HTTP errors for JSON-RPC 2.0 requests" (56b6c33)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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));
}
}
else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
@@ -73,8 +73,11 @@ static std::vector<std::vector<std::string>> g_rpcauth; | |||
static std::map<std::string, std::set<std::string>> g_rpc_whitelist; | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: JSONErrorReply
-> JSONRPCErrorReply
, although it could be argued that it actually does write a JSON object in the response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #27101 (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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK cbc6c44. Just suggested changes since the last review: changing uncaught exception error code from PARSE_ERROR to MISC_ERROR, renaming a few things, and adding comments.
} | ||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #27101 (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.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is unused?
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.