-
Notifications
You must be signed in to change notification settings - Fork 225
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
RPC support for CometBFT 0.38 #1317
Conversation
In the v0_37+ dialect helper, support the new finalize_block_events field if it is present. Also default the begin_block_events and end_block_events fields to None if they are not present.
For abci::Event and abci::response::CheckTx, restore the Deserialize implementation on domain types themselves. The event serialization format as used in 0.37+ will be established as the "canonical" serialization.
Add the new fields and types without static differentiation for 0.38.x. Use the domain type ExecTxResult in deserializing the endpoint response types in the current (i.e. 0.37+) dialect for these endpoints: - /block_results - /broadcast_tx_commit - /tx - /tx_commit The Deserialize impls are restored on the response types, corresponding to the JSON schema for the current RPC dialect. Simplify the 0.34 dialect support for these responses, no longer using generics. In endpoint::tx_commit::Response, rename the deliver_tx field to tx_result to correspond to the current JSON schema. The Deserialize impl falls back to deliver_tx to also be able to parse 0.37 responses.
Some serde attributes were not migrated onto the ExecTxResult domain type when it has been tasked with deserializing the latest RPC dialect. On abci::response::CheckTx, add serde(default) on fields that are no longer present in CometBFT 0.38.
If the finalize_block_events is not present, default to an empty vector. Conversely, skip the field from serializing if it's set to an empty vector.
BeginBlock, EndBlock, FinalizeBlock are used to deserialize RPC responses.
Now that event data can have new fields, distinguish this with a new NewBlock enum variant, renaming the old one to LegacyNewBlock. The dialect serialization helpers for subscription events are separated to v0_34::DialectEvent, that does not recognize the 0.38 fields at all and parses the event attributes as base64-encoded, and latest::DialectEvent, which has provisions for 0.38 fields if they are present, and in this case converts to the NewBlock variant.
Codecov Report
@@ Coverage Diff @@
## main #1317 +/- ##
=======================================
+ Coverage 58.1% 59.8% +1.6%
=======================================
Files 281 283 +2
Lines 25478 26895 +1417
=======================================
+ Hits 14824 16093 +1269
- Misses 10654 10802 +148
... and 5 files with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
For consistency, define event::v0_37 and event::v0_38, aliasing the public definitions in event::latest.
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.
Amazing work @mzabaluev! I just have some questions which I've left inline.
@@ -49,6 +49,7 @@ impl CompatMode { | |||
match (version.major, version.minor) { | |||
(0, 34) => Ok(CompatMode::V0_34), | |||
(0, 37) => Ok(CompatMode::V0_37), | |||
(0, 38) => Ok(CompatMode::V0_37), |
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.
This seems a bit counterintuitive, though I guess if the RPC client does not differentiate between 0.37 and 0.38 it makes sense.
I wonder though if we could rather add a CompatMode
variant for 0.38 and then change the logic at use site to check whether the compat mode is set to 0.3X or higher on case-per-case basis, eg. with compat_mode >= CompatMode::v0_37
for when we want to check if the version is higher or equal to v0.37.
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.
Correct, the RPC "dialect" does not change enough between 0.37 and 0.38 that we have to specially switch anything in order to talk to any node built on either of these versions. Naming could be improved; I tried to play with the Latest
compact mode name in the past, perhaps anticipating this ambiguity.
/// | ||
/// The JSON field carrying this data is named `deliver_tx` in | ||
/// CometBFT versions before 0.38. | ||
#[serde(alias = "deliver_tx")] |
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.
Let's perhaps add a comment to the whole struct to specify why we cannot derive Serialize
here (ie. because of the field name change between <0.38 and >=0.38.
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.
But we do derive Serialize
on the struct, and it will serialize to the latest RPC schema, 0.38.
The DialectResponse
from the submodule can be used to serialize in the 0.34 schema.
And thanks for bringing this up: this means we have nothing to serialize with for 0.37 JSON-RPC if anyone wants to. The RPC client does not use Serialize
for responses, so I've been treating it as an afterthought. Maybe there should be another compatibility submodule just to enable this.
rpc/src/event.rs
Outdated
gas_used: msg.gas_used, | ||
events: msg.events.into_iter().map(Into::into).collect(), | ||
/// Serialization helpers for the RPC protocol used since CometBFT 0.37 | ||
pub mod latest { |
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.
Rather than have the latest
module define directly items for the latest supported release, can we instead name it with the version for that release and have the latest
module re-exports everything from that module?
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.
The helpers in this module are actually a grab bag for deserializing anything that might be present in 0.37 or 0.38.
On the other hand, the Serialize
impl can also emit any fields that are not set to None
, which is rather sloppy.
I'll think on how to improve this in a more principled way.
pub consensus_param_updates: Option<consensus::Params>, | ||
// FIXME: decide if base64 is the canonical serialization for AppHash, |
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.
Just a note that there is a remaining FIXME here
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.
Herein lies a question: do we want a "normal" way to serialize AppHash
, even though in the current protocol there are instances of both hex and base64 encoding? I've asked this on Slack, but nobody provided an answer.
#[derive(Debug, Clone, PartialEq, Eq)] | ||
// To be fixed in 0.24 | ||
#[allow(clippy::large_enum_variant)] | ||
pub enum EventData { |
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.
Have we considered making EventData
part of the dialect rather than introduce a legacy variant for NewBlock?
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 thought of making the NewBlock
variant in this API type carry both 0.37 and 0.38 fields as optionals just like in latest::DialectEventData
, and decoding to a different set of fields getting Some
values for each respective version, but this feels like we'd be ramifying the enum matching that typically needs to be done by the consumer across multiple levels of the data structure.
So I decided a more clean split of the old and the new data is needed, also because we should eventually deprecate and remove LegacyNewBlock
when everything up to 0.37 is EOLd.
Or do you have another idea about the API for this? Consider the dialect stuff in submodules an implementation detail for decoding JSON-RPC; I think the whole dialect business should be made private, the only reason I haven't made it private is the "integration" tests that decode fixtures, but those more properly belong in unit tests.
NB: Hermes currently does not care about the changed fields at all, it just needs block
as can be seen in informalsystems/hermes#3372.
The Serialize impl needs to be implemented differently for 0.37 and 0.38 in order to emit old or new fields for NewBlock. The Deserialize impl, however, flexibly handles both formats to avoid introducing another dialect where most other helpers would be identical. This means that Event gets two different helpers for each protocol version: DeEvent for Deserialize and SerEvent for Serialize.
Provide v0_37::DialectResponse as a way to serialize responses in the 0.37 JSON format.
Use the exact protocol version to serialize test server responses as appropriate. No new variant is added to CompatMode, as that is a client-side setting and the client is able to cope with both versions dynamically.
enum TestRpcVersion { | ||
V0_34, | ||
V0_37, | ||
V0_38, | ||
} |
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.
It's almost like CompatMode
could be given the new V0_38
variant as well, but it's avoidable on the client side and I think it would result in lots of unnecessary code duplication.
No opinions have been expressed about any canonical way to serialize app hashes, so the explicit serializer helper should be there for now.
The RPC changes for #1301
.changelog/