use super::{ChainConfig, event_query_error};
use alloy::contract::Error;
use eyre::Result;
use std::future::Future;
pub async fn query<T, F, Fut>(config: ChainConfig, from: u64, to: u64, mut request: F) -> Result<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = std::result::Result<T, Error>>,
{
let mut retries = 0;
loop {
match request().await {
Ok(value) => return Ok(value),
Err(error) => {
if config.id != 1 || !archive_refusal(&error) || retries == 3 {
return Err(event_query_error(config, from, to, error));
}
retries += 1;
tokio::time::sleep(std::time::Duration::from_millis(100 * retries)).await;
}
}
}
}
fn archive_refusal(error: &Error) -> bool {
const PHRASE: &str = "archive requests require a personal token";
let Error::TransportError(transport) = error else {
return false;
};
if let Some(rpc) = transport.as_error_resp() {
return rpc.code == -32602 && rpc.message.to_ascii_lowercase().contains(PHRASE);
}
let Some(http) = transport
.as_transport_err()
.and_then(|kind| kind.as_http_error())
else {
return false;
};
if http.status != 403 {
return false;
}
let Ok(body) = serde_json::from_str::<serde_json::Value>(&http.body) else {
return false;
};
body["error"]["code"].as_i64() == Some(-32602)
&& body["error"]["message"]
.as_str()
.is_some_and(|message| message.to_ascii_lowercase().contains(PHRASE))
}