Skip to content

jsonrpc2: Add batch and notify support - #49

Open
shazow wants to merge 11 commits into
pool-metadatafrom
jsonrpc2-batch
Open

jsonrpc2: Add batch and notify support#49
shazow wants to merge 11 commits into
pool-metadatafrom
jsonrpc2-batch

Conversation

@shazow

@shazow shazow commented Jul 5, 2019

Copy link
Copy Markdown
Member

This should get us very close to #43. The goal is to have a complete JSONRPC2 implementation with a permissive license that can be usable for all functions of Ethereum nodes. (Aside: Might even be worth pulling it out into a standalone library at that point, to allow for separate versioning?)

  • Add support for batched messages. (Closes jsonrpc2: Add batch request support #42)
    • Read: Batched messages are unbatched in the consumer API, so the consumer can pretend they're individual messages.
    • Write: Introduced a new Replier interface, which the internal API uses to reply to messages. When a message is part of a batch, replying to it will buffer it into a batch reply internally until the correct number of replies are accumulated, then they're flushed as a batch.
  • Add support for notifications (messages without IDs, ignored in batch replies), callable via the new Notify interface. (Closes jsonrpc2: Add notification support #41)

Comment thread jsonrpc2/server.go
},
ID: req.ID,
Version: Version,
func (s *Server) Handle(ctx context.Context, req *Request) *Response {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is breaking the public API, but nobody is using it yet and it's moderately internal, sooo seems safe? 🤷‍♂️

@shazow
shazow requested a review from ryanschneider July 8, 2019 20:48

@ryanschneider ryanschneider left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall lgtm, minor questions/comments below.

Overall, I agree w/ breaking it out into it's own 0.x-versioned repo.

Comment thread jsonrpc2/client.go
}

// newNotification is a helper for creating encoded Request Messages without IDs.
func newNotification(method string, params ...interface{}) (*Message, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not add Notification as a public method and to the Requester interface? Or perhaps a new Notifier interface?

@shazow shazow Jul 9, 2019

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Mostly trying to reduce the public API surface. I imagine there are two use flows for this kind of library: Either you're using it at the highest level (Call(...)) and never have to think about the message types, or you're using it at the lowest level and passing around message instances through codecs.

These newNotification/newRequest helpers are kind-of in-between (really they're helpers for going from low-level to high-level). If you're constructing your own message types, you probably have enough custom stuff that you're not going to use this. If you're using the high-level interface, you're not going to use this either.

Also they're just the byproduct of an internal refactor, so it's not something that was exposed before.

Comment thread jsonrpc2/codecs.go
@@ -26,31 +52,121 @@ var _ Codec = &jsonCodec{}
// IOCodec returns a Codec that wraps JSON encoding and decoding over IO.
func IOCodec(rwc io.ReadWriteCloser) *jsonCodec {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Intentional discrepancy? Public function is IOCodec but it returns a jsonCodec?

@shazow shazow Jul 9, 2019

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

All the *Codec helpers return a Codec interface implementation. In this case, it takes an IO (rwc) and returns a Codec (implemented by jsonCodec), so it's an IOCodec.

I don't return the Codec interface explicitly to comply with the "accept interfaces, return structs" mantra.

The fact that it's a jsonCodec is because it is json RPC after all. :)

I should probably:

  1. Pull all these codecs into a codec sub-package, then could have jsonrpc2.codec.New(rwc)
  2. Maybe make the jsonCodec struct a public JSONCodec so people can directly provide their own encoders/decoders.

I've been going back and forth on how I wanted the jsonCodec internals to be used, but it might be stable enough now.

Comment thread jsonrpc2/codecs.go Outdated
if len(batch) == 0 {
// FIXME: Should we be forgiving of this? Just return an empty message,
// or keep blocking until we get one?
return nil, errors.New("empty message batch")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

According to the spec:

If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value, the response from the Server MUST be a single Response object

https://www.jsonrpc.org/specification#batch

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also from the examples:

--> []
<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch on that in the specs, I missed it!

I'm preeeetty sure the outcome will be correct, because the error at the codec level will get converted into a JSONRPC error message at the transport level.

I should totally add a test for this.

Interestingly, it seems geth's jsonrpc implementation ignores empty batches.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In fact, I should add all the examples as tests.

@shazow shazow Jul 9, 2019

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You were right, needed to wire up the actual response error. (Edit: Got a few more changes coming up, hang tight)

Comment thread jsonrpc2/codecs_test.go
},
{
In: `
[

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this actually desired behavior?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@ryanschneider How do you mean?

I'm probably missing something, but looks sensible to me. Note that the input is a JSON stream, so multiple messages are supported.

@shazow

shazow commented Jul 9, 2019

Copy link
Copy Markdown
Member Author

Leaning towards pulling this out into a standalone lib now, instead of even merging changes here.

@shazow

shazow commented Jul 9, 2019

Copy link
Copy Markdown
Member Author

Maybe take the opportunity to reduce the public API surface, too.

@shazow

shazow commented Jul 9, 2019

Copy link
Copy Markdown
Member Author

Going to move around where the batch errors are generated, and add a bunch more tests. Will ping again when it's ready for another look. :)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants