paritytech / jsonrpsee

Rust JSON-RPC library on top of async/await
MIT License
625 stars 168 forks source link

Subscriptions just returns None (no error) - manual tokio_tungstenite works fine #1142

Open chikko80 opened 1 year ago

chikko80 commented 1 year ago

I am trying to connect to a jsonrpc websocket with your library but it doesn't work. The strange thing is that every other library including manual implementation with tokio-tokio_tungstenite works absolutely fine. Even a simple wscat via terminal works without any problems.

from stream.next().await i always get just a None - there is no error. Seems like the connection gets successfully established but then there is just nothing to receive. Even if I loop the stream.next().await for 1 minute i never receive anything. Super confused cause its just with jsonrpsee which would totally fit for my use case.

Is anyone familiar with that problem or has an idea how to solve that?

    #[tokio::test]
    async fn test_jsonrpsee() {
        let mut headers = HeaderMap::new();
        let bx_auth_header = "<bloxroute-auth-token>";
        headers.insert("Authorization", bx_auth_header.parse().unwrap());

        let ws_client = WsClientBuilder::default().set_headers(headers);

        let client = ws_client
            .build("wss://api.blxrbdn.com:443/ws")
            .await
            .unwrap();
        dbg!(&client);

        // let bx_params = BxParams::default();
        // dbg!(bx_params.clone().to_rpc_params());
        let params = rpc_params!["newTxs", doc! {"include": vec!["tx_hash"]}];

        let mut stream: Subscription<String> = client
            .subscribe("subscribe", params, "unsubscribe")
            .await
            .unwrap();

        tokio::time::sleep(Duration::from_secs(1)).await;

        dbg!(&stream);
        dbg!(stream.next().await);
    }

    #[tokio::test]
    async fn test_manual_impl() {
        let request_text = r#"{
            "jsonrpc": "2.0",
            "id": 0,
            "method": "subscribe",
            "params": ["newTxs", {"include": ["tx_hash"]}]
        }"#;

        // Generate a random Sec-WebSocket-Key
        let key = base64::encode("secret-key");

        let request = Request::get("wss://api.blxrbdn.com:443/ws")
            .header("Host", "api.blxrbdn.com:443")
            .header("Authorization", "<bloxroute-auth-token>")
            .header("Sec-WebSocket-Key", key)
            .header("Connection", "Upgrade")
            .header("Upgrade", "websocket")
            .header("Sec-WebSocket-Version", "13")
            .body(())
            .unwrap();

        let (mut ws_stream, _) = connect_async(request).await.expect("Failed to connect");

        ws_stream
            .send(Message::Text(request_text.to_string()))
            .await
            .expect("Failed to send a message");

        dbg!(ws_stream.next().await);
    }
niklasad1 commented 1 year ago

Hey @chikko80

It's bit hard to understand what the issue is from my side of things, if you could instantiate a tracing subscriber and use RUST_LOG=jsonrpsee=trace and paste the logs that would be a big help.

Do you know the format of subscription notifications as the server sends out?

Maybe it's interpreted as a close notification or something....

chikko80 commented 1 year ago

@niklasad1 thanks for your fast support - yeah tracing subscriber makes sense seems like there is a parsing error:

2023-06-16T22:58:40.306221Z TRACE subscription{method="subscribe"}: /home/dex/.cargo/registry/src/github.com-1ecc6299db9ec823/jsonrpsee-core-0.18.2/src/tracing.rs:8: send="{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"subscribe\",\"params\":[\"newTxs\",{\"include\":[\"tx_hash\"]}]}"
2023-06-16T22:58:40.306325Z TRACE /home/dex/.cargo/registry/src/github.com-1ecc6299db9ec823/jsonrpsee-client-transport-0.18.2/src/ws/mod.rs:227: send: {"jsonrpc":"2.0","id":0,"method":"subscribe","params":["newTxs",{"include":["tx_hash"]}]}
2023-06-16T22:58:40.419312Z TRACE subscription{method="subscribe"}: /home/dex/.cargo/registry/src/github.com-1ecc6299db9ec823/jsonrpsee-core-0.18.2/src/tracing.rs:34: recv="{\"jsonrpc\":\"2.0\",\"result\":\"9e0b132e-46dd-4d6e-b92b-cab2b6f8a92b\",\"id\":1}"
2023-06-16T22:58:40.519506Z ERROR /home/dex/.cargo/registry/src/github.com-1ecc6299db9ec823/jsonrpsee-core-0.18.2/src/client/async_client/mod.rs:770: [backend]: Custom error: Unparseable message: {"id":0,"method":"subscribe","params":{"subscription":"9e0b132e-46dd-4d6e-b92b-cab2b6f8a92b","result":{"txHash":"0xfef682e838b1a255ec58d87ec0e6dfc42de490a0f6bef59669ad1bb5efd78f78"}}}

Seems like the lib can't parse that format:

{"id":0,"method":"subscribe","params":{"subscription":"9e0b132e-46dd-4d6e-b92b-cab2b6f8a92b","result":{"txHash":"0xfef682e838b1a255ec58d87ec0e6dfc42de490a0f6bef59669ad1bb5efd78f78"}}}

Update:

/// JSON-RPC notification (a request object without a request ID) as defined in the
/// [spec](https://www.jsonrpc.org/specification#request-object).
#[derive(Serialize, Deserialize, Debug)]
pub struct Notification<'a, T> {
    /// JSON-RPC version.
    pub jsonrpc: TwoPointZero,
    /// Name of the method to be invoked.
    #[serde(borrow)]
    pub method: Cow<'a, str>,
    /// Parameter values of the request.
    pub params: T,
}

Seems like the missing \"jsonrpc\":\"2.0\" in the response is the problem right? This is obviously a problem of the endpoint itself cause the protocol is basically jsonrpc. Is there any way to make this work with that library?

niklasad1 commented 1 year ago

Ok, I see.

Previously, we made the interpretation that jsonrpc field must exist but recently https://github.com/paritytech/jsonrpsee/issues/1045 explained to me that it could be interpreted as optional.

So, it's fine to change this as well to

/// JSON-RPC notification (a request object without a request ID) as defined in the
/// [spec](https://www.jsonrpc.org/specification#request-object).
#[derive(Serialize, Deserialize, Debug)]
pub struct Notification<'a, T> {
    /// JSON-RPC version.
    pub jsonrpc: Option<TwoPointZero>,
    /// Name of the method to be invoked.
    #[serde(borrow)]
    pub method: Cow<'a, str>,
    /// Parameter values of the request.
    pub params: T,
}

Are you down to make a PR?

chikko80 commented 1 year ago

@niklasad1 Please checkout the change on my branch. I just made the jsonrpc of the Notification optional. I guess it makes sense to keep the jsonrpc required for the Request struct? I wasn't sure about the NotificationSer struct. Don't really understand the usecase of that one. Let me know what you think about / if i should change anything else