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 Protobuf typed/defined Penumbra ABCI Events #3492

Merged
merged 7 commits into from
Dec 11, 2023

Conversation

ejmg
Copy link
Collaborator

@ejmg ejmg commented Dec 7, 2023

PR for #3460.

Comment on lines 13 to 16
// NOTE: Clarify if the serde derivation is pbjson/prost aware. Or, more specifically, if Penumbra generated serialization code for protobuf defs are more coherent than the
// 3-4 different ways the cosmo-sdk chooses to serializes and deserialize its protobuf data in the reference code.
// TODO: Would it be preferred to return an empty(?) or error coded abci::Event?
let event_json = serde_json::to_vec(&self.encode_to_vec()).expect("ProtoEvent constrained values should be JSON serializeable.");
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I made a mistake specifying the original issue. We use pbjson_build to ensure that all of the Rust proto types generated from .proto files have Serialize/Deserialize impls that produce ProtoJSON output.

So we should change the trait bound to be ProtoEvent: Message + Name + Serialize + Deserialize + Sized and use the Serialize/Deserialize impls directly, rather than doing encode_to_vec.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is clarifying. at some point I realized that I didn't know if there was a cost/benefit to relying on those traits being transitively resolved because of Message.

Copy link
Member

Choose a reason for hiding this comment

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

Technically, they're not -- that's why we need to add them as extra bounds. It just happens to be the case (because of our build configuration) that every Message impl we generate also has a matching Serialize/Deserialize implementation.

In terms of the API, it would be the caller's responsibility to ensure that they only pass Message instances that were generated with pbjson_build-generated Serialize impls. But we don't need to worry about this too much because we do that in our build configs.

// TODO: Would it be preferred to return an empty(?) or error coded abci::Event?
let event_json = serde_json::to_vec(&self.encode_to_vec()).expect("ProtoEvent constrained values should be JSON serializeable.");

let attribute_kv : HashMap<String, String> = serde_json::from_slice(&event_json).expect("A serde derived Vec<u8> should be deserializeable.");
Copy link
Member

Choose a reason for hiding this comment

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

I think this might be easier if instead of using serde_json::to_vec above, the serialization to JSON was done with serde_json::value::to_value https://docs.rs/serde_json/latest/serde_json/value/fn.to_value.html

In other words, we would serialize the Rust type to a JSON AST, then walk through the first-level leaves and generate an appropriate k/v pair for each leaf (using the field name as the key, and fully serializing the JSON AST as the value).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

completely missed that as a feature of the crate, thanks!

Comment on lines 50 to 53
let json = serde_json::to_value(attributes).expect("HashMap of String, serde_json::Value should be serializeable.");

// NOTE: Same thing here as into_event(), check what the equivalent to the jsonpb Unmarshaler is.
let json_bytes = serde_json::to_vec(&attributes_kv).expect("Hashmap of EventAttribute Keys and values should be serializeable.");
// let decoded_event = Self::decode(serde_json::from_slice(&json_bytes).expect("Serde derived serialized bytes should be deserializeable."));
// Hitting a lifetime error that requires further restriction on Self: Deserialize that conflicts with the current supertraits defined above.
// return Ok(serde_json::from_value(json)?)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, @hdevalence, all I'm left is figuring out how to get the final bit of equivalent unmarshalling/marshalling code in from_event method so I can do the actual test data/string munging of results. These lines are the rough equivalent to the final lines of the go impl here.

With the final bit twiddling, do I just want serde_json to give me back a value of Result<Self> like I have commented out above? If so, I'm hitting a Trait restriction compile error directly on Self that required an additional lifetime that had me re-writing the Trait's signature to the point I started leaking out the parameterized type variable into the StateWriterTrait trait.

I tried massaging things around but I want to make sure I'm not going on another snipe hunt again.

Copy link
Member

Choose a reason for hiding this comment

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

@hdevalence hdevalence marked this pull request as ready for review December 11, 2023 08:33
ejmg and others added 5 commits December 11, 2023 00:34
- use DeserializeOwned to avoid lifetime bounds
- use the fully qualified proto name for the event type, to avoid conflicts
- add a hardcoded test vector (generated with the current code and manually inspected)
- run rustfmt
This adds the EventOutput case, which exposed some issues with the
(de)serialization logic since it has nested objects.
@hdevalence
Copy link
Member

This looks great! I added a few commits to carry it over the finish line. In particular:

  • using DeserializeOwned avoids the lifetime bounds
  • using the fully-qualified proto name will avoid potential conflicts between events in different modules.

I had to make some changes to the (de)serialization to make it work with the EventOutput, as an example of something with nested substructures.

In the original issue there was a suggestion to replace all of the existing events with proto-events. I don't think we should do that before merging this, it could be a separate issue. The events are not consensus-critical, so we can change them later (though it's slightly annoying to have different event formats in the same database or otherwise to re-execute all the blocks, etc)

@hdevalence hdevalence merged commit b561823 into main Dec 11, 2023
5 checks passed
@hdevalence hdevalence deleted the ejmg/3460-protobuf-abci-events branch December 11, 2023 08:47
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