use std::fmt::Debug;
use alloy::primitives::B256;
use async_trait::async_trait;
use backoff::ExponentialBackoff;
use chrono::TimeDelta;
use reqwest::{Client, Url};
#[cfg(test)]
use mockall::automock;
use types::{BlobscanBlock, HandleReorgRequestBody};
use crate::{clients::common::ClientResult, json_get, json_put};
use self::{
jwt_manager::{Config as JWTManagerConfig, JWTManager},
types::{
Blob, Block, BlockchainSyncState, GetSyncStateResponse, IndexRequestBody, Transaction,
UpdateSyncStateRequestBody,
},
};
mod jwt_manager;
pub mod types;
#[async_trait]
#[cfg_attr(test, automock)]
pub trait CommonBlobscanClient: Send + Sync + Debug {
fn try_with_client(client: Client, config: Config) -> ClientResult<Self>
where
Self: Sized;
async fn index(
&self,
block: Block,
transactions: Vec<Transaction>,
blobs: Vec<Blob>,
) -> ClientResult<()>;
async fn get_block(&self, slot: u32) -> ClientResult<Option<BlobscanBlock>>;
async fn handle_reorg(
&self,
rewinded_blocks: Vec<B256>,
forwarded_blocks: Vec<B256>,
) -> ClientResult<()>;
async fn update_sync_state(&self, sync_state: BlockchainSyncState) -> ClientResult<()>;
async fn get_sync_state(&self) -> ClientResult<Option<BlockchainSyncState>>;
}
#[derive(Debug, Clone)]
pub struct BlobscanClient {
base_url: Url,
client: reqwest::Client,
jwt_manager: JWTManager,
exp_backoff: Option<ExponentialBackoff>,
}
pub struct Config {
pub base_url: String,
pub secret_key: String,
pub exp_backoff: Option<ExponentialBackoff>,
}
#[async_trait]
impl CommonBlobscanClient for BlobscanClient {
fn try_with_client(client: Client, config: Config) -> ClientResult<Self> {
let base_url = Url::parse(&format!("{}/", config.base_url))?;
let jwt_manager = JWTManager::new(JWTManagerConfig {
secret_key: config.secret_key,
refresh_interval: TimeDelta::try_hours(1).unwrap(),
safety_magin: None,
});
let exp_backoff = config.exp_backoff;
Ok(Self {
base_url,
client,
jwt_manager,
exp_backoff,
})
}
async fn index(
&self,
block: Block,
transactions: Vec<Transaction>,
blobs: Vec<Blob>,
) -> ClientResult<()> {
let url = self.base_url.join("indexer/block-txs-blobs")?;
let token = self.jwt_manager.get_token()?;
let body = IndexRequestBody {
block,
transactions,
blobs,
};
json_put!(&self.client, url, token, &body, self.exp_backoff.clone()).map(|_: Option<()>| ())
}
async fn get_block(&self, slot: u32) -> ClientResult<Option<BlobscanBlock>> {
let url = self.base_url.join(&format!("slots/{}", slot))?;
json_get!(&self.client, url, BlobscanBlock, self.exp_backoff.clone())
}
async fn handle_reorg(
&self,
rewinded_blocks: Vec<B256>,
forwarded_blocks: Vec<B256>,
) -> ClientResult<()> {
let url = self.base_url.join("indexer/reorged-blocks")?;
let token = self.jwt_manager.get_token()?;
let body = HandleReorgRequestBody {
forwarded_blocks,
rewinded_blocks,
};
json_put!(
&self.client,
url,
HandleReorgRequestBody,
token,
&body,
self.exp_backoff.clone()
)
.map(|_| ())
}
async fn update_sync_state(&self, sync_state: BlockchainSyncState) -> ClientResult<()> {
let url = self.base_url.join("blockchain-sync-state")?;
let token = self.jwt_manager.get_token()?;
let body: UpdateSyncStateRequestBody = sync_state.into();
json_put!(&self.client, url, token, &body, self.exp_backoff.clone()).map(|_: Option<()>| ())
}
async fn get_sync_state(&self) -> ClientResult<Option<BlockchainSyncState>> {
let url = self.base_url.join("blockchain-sync-state")?;
json_get!(
&self.client,
url,
GetSyncStateResponse,
self.exp_backoff.clone()
)
.map(|res: Option<GetSyncStateResponse>| Some(res.unwrap().into()))
}
}