feat(provider): check for serde error with missing req id
use super::{common::JsonRpcError, http::ClientError};
use crate::{provider::ProviderError, JsonRpcClient};
use async_trait::async_trait;
-use serde::{de::DeserializeOwned, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
fmt::Debug,
sync::atomic::{AtomicU32, Ordering},
@@ -347,11 +347,8 @@ pub struct HttpRateLimitRetryPolicy;
impl RetryPolicy<ClientError> for HttpRateLimitRetryPolicy {
fn should_retry(&self, error: &ClientError) -> bool {
- match error {
- ClientError::ReqwestError(err) => {
- err.status() == Some(http::StatusCode::TOO_MANY_REQUESTS)
- }
- ClientError::JsonRpcError(JsonRpcError { code, message, .. }) => {
+ fn should_retry_json_rpc_error(err: &JsonRpcError) -> bool {
+ let JsonRpcError { code, message, .. } = err;
// alchemy throws it this way
if *code == 429 {
return true
@@ -370,7 +367,25 @@ impl RetryPolicy<ClientError> for HttpRateLimitRetryPolicy {
_ => false,
}
}
- _ => false,
+
+ match error {
+ ClientError::ReqwestError(err) => {
+ err.status() == Some(http::StatusCode::TOO_MANY_REQUESTS)
+ }
+ ClientError::JsonRpcError(err) => should_retry_json_rpc_error(err),
+ ClientError::SerdeJson { text, .. } => {
+ // some providers send invalid JSON RPC in the error case (no `id:u64`), but the
+ // text should be a `JsonRpcError`
+ #[derive(Deserialize)]
+ struct Resp {
+ error: JsonRpcError,
+ }
+
+ if let Ok(resp) = serde_json::from_str::<Resp>(text) {
+ return should_retry_json_rpc_error(&resp.error)
+ }
+ false
+ }
}
}
@@ -526,4 +541,17 @@ mod tests {
let should_retry = HttpRateLimitRetryPolicy::default().should_retry(&err);
assert!(should_retry);
}
+
+ #[test]
+ fn test_rate_limit_omitted_id() {
+ let s = r#"{"jsonrpc":"2.0","error":{"code":-32016,"message":"Your IP has exceeded its requests per second capacity. To increase your rate limits, please sign up for a free Alchemy account at https://www.alchemy.com/optimism."},"id":null}"#;
+
+ let err = ClientError::SerdeJson {
+ err: serde::de::Error::custom("unexpected notification over HTTP transport"),
+ text: s.to_string(),
+ };
+
+ let should_retry = HttpRateLimitRetryPolicy::default().should_retry(&err);
+ assert!(should_retry);
+ }
}
