partner_chains_dolos_data_sources/client/
minibf.rs

1use async_trait::async_trait;
2use blockfrost_openapi::models::{
3	address_transactions_content_inner::AddressTransactionsContentInner,
4	address_utxo_content_inner::AddressUtxoContentInner,
5	asset_addresses_inner::AssetAddressesInner, asset_transactions_inner::AssetTransactionsInner,
6	block_content::BlockContent, epoch_param_content::EpochParamContent,
7	epoch_stake_pool_content_inner::EpochStakePoolContentInner, genesis_content::GenesisContent,
8	pool_history_inner::PoolHistoryInner, pool_list_extended_inner::PoolListExtendedInner,
9	tx_content::TxContent, tx_content_utxo::TxContentUtxo,
10};
11use serde::de::DeserializeOwned;
12use sidechain_domain::*;
13use std::time::Duration;
14use ureq::Agent;
15
16use crate::{
17	DataSourceError,
18	client::api::{McBlockId, McPoolId, MiniBFApi},
19};
20
21/// Client implementing Dolos MiniBF
22#[derive(Clone)]
23pub struct MiniBFClient {
24	agent: ureq::Agent,
25	addr: String,
26}
27
28impl MiniBFClient {
29	pub fn new(addr: &str, timeout: Duration) -> Self {
30		let agent = Agent::config_builder().timeout_per_call(Some(timeout)).build().into();
31		MiniBFClient { agent, addr: addr.strip_suffix("/").unwrap_or(addr).to_string() }
32	}
33
34	async fn request<T: DeserializeOwned + std::fmt::Debug>(
35		&self,
36		method: &str,
37	) -> Result<T, DataSourceError> {
38		let req = format!("{}/{}", self.addr, method);
39		log::trace!("Dolos request: {req:?}");
40		let resp = self
41			.agent
42			.get(req)
43			.call()
44			.map_err(|e| DataSourceError::DolosCallError(e.to_string()))
45			.and_then(|mut r| {
46				r.body_mut()
47					.read_json()
48					.map_err(|e| DataSourceError::DolosResponseParseError(e.to_string()))
49			});
50		log::trace!("Dolos response: {resp:?}");
51		resp
52	}
53
54	async fn paginated_request<T: DeserializeOwned + std::fmt::Debug>(
55		&self,
56		method: &str,
57		pagination: Pagination,
58	) -> Result<Vec<T>, DataSourceError> {
59		let mut query_pairs = url::form_urlencoded::Serializer::new(String::new());
60		query_pairs.extend_pairs([
61			("count", &pagination.count.to_string()),
62			("page", &pagination.page.to_string()),
63			("order", &pagination.order.to_string()),
64		]);
65		if let Some(from) = pagination.from {
66			query_pairs.append_pair("from", &from);
67		}
68		if let Some(to) = pagination.to {
69			query_pairs.append_pair("to", &to);
70		}
71		let mut req_url =
72			url::Url::parse(&format!("{}/{}", self.addr, method)).expect("valid Dolos url");
73		req_url.set_query(Some(&query_pairs.finish()));
74		log::trace!("Dolos request: {req_url:?}");
75		let resp = self
76			.agent
77			.get(req_url.as_str())
78			.call()
79			.map_err(|e| DataSourceError::DolosCallError(e.to_string()))
80			.and_then(|mut r| {
81				r.body_mut()
82					.read_json()
83					.map_err(|e| DataSourceError::DolosResponseParseError(e.to_string()))
84			});
85		log::trace!("Dolos response: {resp:?}");
86		resp
87	}
88
89	async fn paginated_request_all<T: DeserializeOwned + std::fmt::Debug>(
90		&self,
91		method: &str,
92	) -> Result<Vec<T>, DataSourceError> {
93		let mut pagination: Pagination = Pagination::default();
94		let mut have_all_pages = false;
95		let mut res = Vec::new();
96		while !have_all_pages {
97			let mut resp: Vec<T> = self.paginated_request(method, pagination.clone()).await?;
98			if (resp.len() as i32) < pagination.count {
99				have_all_pages = true
100			}
101			res.append(&mut resp);
102			pagination.page += 1;
103		}
104		Ok(res)
105	}
106}
107
108#[async_trait]
109impl MiniBFApi for MiniBFClient {
110	async fn addresses_utxos(
111		&self,
112		address: MainchainAddress,
113	) -> Result<Vec<AddressUtxoContentInner>, DataSourceError> {
114		self.paginated_request_all(&format!("addresses/{address}/utxos")).await
115	}
116
117	async fn addresses_transactions(
118		&self,
119		address: MainchainAddress,
120	) -> Result<Vec<AddressTransactionsContentInner>, DataSourceError> {
121		self.paginated_request_all(&format!("addresses/{address}/transactions")).await
122	}
123
124	async fn assets_transactions(
125		&self,
126		asset_id: AssetId,
127	) -> Result<Vec<AssetTransactionsInner>, DataSourceError> {
128		let asset_id_str = format_asset_id(&asset_id);
129		self.paginated_request_all(&format!("assets/{asset_id_str}/transactions")).await
130	}
131
132	async fn assets_addresses(
133		&self,
134		asset_id: AssetId,
135	) -> Result<Vec<AssetAddressesInner>, DataSourceError> {
136		let asset_id_str = format_asset_id(&asset_id);
137		self.paginated_request_all(&format!("assets/{asset_id_str}/addresses")).await
138	}
139
140	async fn blocks_latest(&self) -> Result<BlockContent, DataSourceError> {
141		self.request("blocks/latest").await
142	}
143
144	async fn blocks_by_id(
145		&self,
146		id: impl Into<McBlockId> + Send,
147	) -> Result<BlockContent, DataSourceError> {
148		let id: McBlockId = id.into();
149		self.request(&format!("blocks/{id}")).await
150	}
151
152	async fn blocks_slot(
153		&self,
154		slot_number: McSlotNumber,
155	) -> Result<BlockContent, DataSourceError> {
156		self.request(&format!("blocks/slot/{slot_number}")).await
157	}
158
159	async fn blocks_next(
160		&self,
161		id: impl Into<McBlockId> + Send,
162	) -> Result<Vec<BlockContent>, DataSourceError> {
163		let id: McBlockId = id.into();
164		self.request(&format!("blocks/{id}/next")).await
165	}
166
167	async fn blocks_txs(
168		&self,
169		id: impl Into<McBlockId> + Send,
170	) -> Result<Vec<String>, DataSourceError> {
171		let id: McBlockId = id.into();
172		self.request(&format!("blocks/{id}/txs")).await
173	}
174
175	async fn epochs_blocks(
176		&self,
177		epoch_number: McEpochNumber,
178	) -> Result<Vec<String>, DataSourceError> {
179		self.paginated_request_all(&format!("epochs/{epoch_number}/blocks")).await
180	}
181	async fn epochs_parameters(
182		&self,
183		epoch_number: McEpochNumber,
184	) -> Result<EpochParamContent, DataSourceError> {
185		self.request(&format!("epochs/{epoch_number}/parameters")).await
186	}
187	async fn epochs_stakes_by_pool(
188		&self,
189		epoch_number: McEpochNumber,
190		pool_id: impl Into<McPoolId> + Send,
191	) -> Result<Vec<EpochStakePoolContentInner>, DataSourceError> {
192		let pool_id: McPoolId = pool_id.into();
193		self.paginated_request_all(&format!("epochs/{epoch_number}/stakes/{pool_id}"))
194			.await
195	}
196
197	async fn pools_history(
198		&self,
199		pool_id: impl Into<McPoolId> + Send,
200	) -> Result<Vec<PoolHistoryInner>, DataSourceError> {
201		let pool_id: McPoolId = pool_id.into();
202		self.paginated_request_all(&format!("pools/{pool_id}/history")).await
203	}
204	async fn pools_extended(&self) -> Result<Vec<PoolListExtendedInner>, DataSourceError> {
205		self.paginated_request_all("pools/extended").await
206	}
207
208	async fn scripts_datum_hash(
209		&self,
210		datum_hash: &str,
211	) -> Result<Vec<serde_json::Value>, DataSourceError> {
212		self.request(&format!("scripts/datum/{datum_hash}")).await
213	}
214
215	async fn transaction_by_hash(&self, tx_hash: McTxHash) -> Result<TxContent, DataSourceError> {
216		self.request(&format!("txs/{tx_hash}")).await
217	}
218
219	async fn transactions_utxos(
220		&self,
221		tx_hash: McTxHash,
222	) -> Result<TxContentUtxo, DataSourceError> {
223		self.request(&format!("txs/{tx_hash}/utxos")).await
224	}
225
226	async fn genesis(&self) -> Result<GenesisContent, DataSourceError> {
227		self.request("genesis").await
228	}
229}
230
231pub fn format_asset_id(asset_id: &AssetId) -> String {
232	let AssetId { policy_id, asset_name } = asset_id;
233	format!("{}{}", &policy_id.to_hex_string()[2..], &asset_name.to_hex_string()[2..])
234}
235
236#[derive(Clone)]
237#[allow(dead_code)]
238enum Order {
239	Asc,
240	Desc,
241}
242
243impl std::fmt::Display for Order {
244	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245		match self {
246			Order::Asc => write!(f, "asc"),
247			Order::Desc => write!(f, "desc"),
248		}
249	}
250}
251
252#[derive(Clone)]
253struct Pagination {
254	count: i32,
255	page: i32,
256	order: Order,
257	from: Option<String>,
258	to: Option<String>,
259}
260
261impl Default for Pagination {
262	fn default() -> Self {
263		Self { count: 100, page: 1, order: Order::Asc, from: None, to: None }
264	}
265}