informalsystems / tendermint-rs

Client libraries for Tendermint/CometBFT in Rust!
Apache License 2.0
615 stars 225 forks source link

`tendermint::Hash` can not be used as a return value when used with `jsonrpsee` #1474

Open oblique opened 1 week ago

oblique commented 1 week ago

We are from Lumina, a Celestia light node written in Rust. When we started the project we forked tendermint-rs to adjust it to Celestia's modifications. Now we are looking to migrate away form our fork.

What went wrong?

We are using jsonrpsee for calling into Celestia's node API. The API is using some types from tendermint-rs, such as tendermint::Hash.

Any struct that uses tendermint::Hash internally (e.g. SyncState) fails to deserialize and gives the following error:

thread 'sync_state' panicked at rpc/tests/header.rs:109:51:
called `Result::unwrap()` on an `Err` value: ParseError(Error("invalid type: string \"0AAC13B8406BDC81049B4E2F382F75754C5436F7DAC702256E9282EE33A242C4\", expected a borrowed string", line: 0, column: 0))

We debugged the issue and this happens because:

  1. jsonrpsee firsts deserializes to serde_json::Value and from there to SyncState.
  2. tendermint::Hash uses <&str>::deserialize(deserializer).
  3. <&str>::deserialize(deserializer) can not be used on serde_json::Value due to borrowing issues.

The pattern that jsonrpsee uses (i.e. &str -> serde_json::Value -> T) is valid and it may be used from other crates too.

Steps to reproduce

use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct HashTest {
    hash: tendermint::Hash,
}

fn main() {
    let s = r#"{"hash": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"}"#;

    // Works
    serde_json::from_str::<HashTest>(s).unwrap();

    // Fails
    let json_value = serde_json::from_str::<serde_json::Value>(s).unwrap();
    serde_json::from_value::<HashTest>(json_value).unwrap();
}

Definition of "done"

We need to stop using <&str>::deserialize. One way is to use String but allocates. Another way is to use the CowStr we implemented in our fork, which allocates on demand.