Skip to content
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

Add Batch JSON-RPC support (rpc client & server) #1503

Merged
merged 25 commits into from
Mar 10, 2020

Conversation

jakesylvestre
Copy link
Collaborator

@jakesylvestre jakesylvestre commented Dec 4, 2019

This pr aims to address #329 which has been open for a while now by adding bulk request support. This is base on from decred#841 by @davecgh

@jakesylvestre jakesylvestre changed the title Rpcbulk [WIP] Bulk RPC Support Dec 4, 2019
@jakesylvestre jakesylvestre reopened this Dec 4, 2019
@jakesylvestre
Copy link
Collaborator Author

I think we need some sort of bulk handler we can tack other methods on too here.

@torkelrogstad
Copy link
Contributor

@jakesyl Do you want review on this now, or should I hold off until it's less of a WIP?

@jakesylvestre
Copy link
Collaborator Author

@torkelrogstad, let's hold off for now. I'll have this done by EoD. Feel free to check it out in the meantime. I think I know how I want to solve the bulk querying in post mode now.

@jakesylvestre
Copy link
Collaborator Author

Actually @torkelrogstad, I'd love to get your input on the best way to queue these bulk requests. Here's how it's done in python:

image

I'm playing with how to do it in btcd

@jakesylvestre
Copy link
Collaborator Author

I'm thinking of adding a batch method onto client that returns a modified client. Here's the aforementioned implementation

@jakesylvestre jakesylvestre changed the title [WIP] Bulk RPC Support Bulk RPC Support Dec 5, 2019
@jakesylvestre
Copy link
Collaborator Author

@torkelrogstad I'm sure we need some work here, but this is what I've got so far. Please review and let me know what you think

@jakesylvestre jakesylvestre changed the title Bulk RPC Support Add Batch JSON-RPC support Dec 5, 2019
@jakesylvestre jakesylvestre changed the title Add Batch JSON-RPC support Add Batch JSON-RPC support (rpc client & server) Dec 5, 2019
@torkelrogstad
Copy link
Contributor

I've started looking it over, great stuff! Review is still pending, I'll wait until I've finished the entire PR before publishing. Hopefully I'll find some time later today.

@jakesylvestre
Copy link
Collaborator Author

Hey, any idea if any changes are required here?

Copy link
Contributor

@torkelrogstad torkelrogstad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for delaying this so much! But here's some review for you.

Tested this against bitcoind 0.19 on my machine, worked great. 🎉

request.ID = data["id"]
methodValue, hasMethod := data["method"]
if hasMethod {
request.Method = methodValue.(string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast here and on line 93 seems a bit dangerous to me, would blow up if the fields are accessed later and it turns out we have the wrong type.
What's your thoughts on this?

metod, ok := methodValue.(string)
if ok {
  request.Method = method
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point. I don't see an alternative though

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torkelrogstad I've gone ahead and addressed this on #1583

image

Comment on lines 95 to 116
paramsValue, hasParams := data["params"]
if !hasParams {
// set the request param to an empty array if it is ommited in the request
request.Params = []json.RawMessage{}
}
if hasParams {
// assert the request params is an array of data
params, paramsOk := paramsValue.([]interface{})
if paramsOk {
rawParams := make([]json.RawMessage, 0, len(params))
for _, param := range params {
marshalledParam, err := json.Marshal(param)
if err != nil {
return err
}
rawMessage := json.RawMessage(marshalledParam)
rawParams = append(rawParams, rawMessage)
}

request.Params = rawParams
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can avoid some nesting this way:

	paramsValue, hasParams := data["params"]
	if !hasParams {
		// set the request param to an empty array if it is ommited in the request
		request.Params = []json.RawMessage{}
		// assert the request params is an array of data
	} else if params, paramsOk := paramsValue.([]interface{}); paramsOk {
		rawParams := make([]json.RawMessage, 0, len(params))
		for _, param := range params {
			marshalledParam, err := json.Marshal(param)
			if err != nil {
				return err
			}
			rawMessage := json.RawMessage(marshalledParam)
			rawParams = append(rawParams, rawMessage)
		}

		request.Params = rawParams
	} else {
		// error here, saying we got the wrong type of data?
	}

// request.
func NewRequest(rpcVersion string, id interface{}, method string, params []interface{}) (*Request, error) {
// default to JSON-RPC 1.0 if RPC type is not specified
if rpcVersion != "2.0" && rpcVersion != "1.0" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strikes me as a bit dangerous. Two potential solutions, could (should, perhaps) do both IMO:

  1. Introduce a type for it
type rpcVersion string
var (
  rpcVersion1 rpcVersion = rpcVersion("1.0")
  rpcVersion2 rpcVersion = rpcVersion("2.0")
)
  1. I think it's better to error out here, instead of silently changing behavior.

// Typically callers will instead want to create the fully marshalled JSON-RPC
// response to send over the wire with the MarshalResponse function.
func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
func NewResponse(rpcVersion string, id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
if rpcVersion != "2.0" && rpcVersion != "1.0" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about RPC versions from above apply here

// a JSON-RPC response byte slice that is suitable for transmission to a
// JSON-RPC client.
func MarshalResponse(rpcVersion string, id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
if rpcVersion != "2.0" && rpcVersion != "1.0" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RPC version comment from above

if len(batchedRequests) == 0 {
jsonErr := &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprint("Invalid request: empty batch"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use string literal directly, no need for fmt.Sprint

rpcwebsocket.go Outdated
if req.Method == "" || req.Params == nil {
jsonErr := &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprintf("Invalid request: malformed"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use string literal directly


jsonErr := &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprint("Invalid request: empty batch"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use string literal directly

rpcwebsocket.go Outdated
if req.Method == "" || req.Params == nil {
jsonErr := &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprintf("Invalid request: malformed"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use string literal directly

// the best block in the longest block chain.
func (r FutureGetBulkResult) Receive() (BulkResult, error) {
m := make(BulkResult)
res, err := receiveFuture(r)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res, err := receiveFuture(r)
if err != nil {
	return nil, err
}

@jakesylvestre
Copy link
Collaborator Author

jakesylvestre commented Feb 9, 2020

Thanks @pavel-main, looks like that fixed it!

See #1537

jakesylvestre added a commit to jakesylvestre/btcd that referenced this pull request Feb 10, 2020
$GOPATH caching has led to flaky tests as per btcsuite#1503 and btcsuite#1536. The speedup is marginal and while the false negatives are a headache, false positives are potentially dangerous.
@jakesylvestre
Copy link
Collaborator Author

Any update here? Looks like prereq #1537 has been fixed

@jakesylvestre
Copy link
Collaborator Author

@jcvernaleo

  • low priority

jakesylvestre added a commit to xplorfin/btcd that referenced this pull request Mar 5, 2020
$GOPATH caching has led to flaky tests as per btcsuite#1503 and btcsuite#1536. The speedup is marginal and while the false negatives are a headache, false positives are potentially dangerous.
@jakesylvestre
Copy link
Collaborator Author

Going to need to squash

Copy link
Member

@jcvernaleo jcvernaleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looking good but along with my question (which I think leads to a lot of unnecessary changes and the questions by @torkelrogstad which need to be resolved, this badly needs to be rebased and squashed. I can do another review after that (and after we resolve those outstanding things) so the set of changes is clearer. I definitely want this to make it in though.

@@ -193,7 +193,7 @@ func TestBtcdExtCmds(t *testing.T) {
for i, test := range tests {
// Marshal the command as created by the new static command
// creation function.
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
marshalled, err := btcjson.MarshalCmd("1.0", testID, test.staticCmd())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you changing from a variable to a hardcoded number here (and all the places below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point, I'll have to refactor. I've held off on squashing till these revisions are complete, but can do this earlier fi it helps your review

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the feedback

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I agree about waiting to squash until revisions are done but this needs the rebase before reviewing it for real so you might as well squash at the same time (I hate making people do extra steps if I don't have to).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem

@jakesylvestre
Copy link
Collaborator Author

@jcvernaleo I merged this by accident, went ahead and reverted instantly.

@jakesylvestre
Copy link
Collaborator Author

can I force push to undo?

@jakesylvestre
Copy link
Collaborator Author

@jcvernaleo merged the wrong branch here - I thought this was on my local. I undid immediately after (#1549), but can't push the history here because the branch is protected :(, please advise

@jakesylvestre
Copy link
Collaborator Author

image
@davecgh tagging you here as well

@jakesylvestre
Copy link
Collaborator Author

btw:
image

@onyb
Copy link
Collaborator

onyb commented May 1, 2020

@jakesyl Are you still actively working on this?

@jakesylvestre
Copy link
Collaborator Author

Hi @onyb yes - I've been very busy the past month but have a few PRs queued here including this one that I need to work on. I'm still active in the btcd irc

@jakesylvestre
Copy link
Collaborator Author

Recreated in #1583

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants