iota_sdk_graphql_client/api/
checkpoints.rs1use cynic::QueryBuilder;
8use futures::Stream;
9use iota_types::{CheckpointDigest, CheckpointSequenceNumber, CheckpointSummary};
10
11use crate::{
12 GraphQLClient,
13 api::define_query,
14 error::GraphQLResult,
15 pagination::{Page, PaginationFilter, PaginationFilterResponse},
16 query_types::{
17 CheckpointArgs, CheckpointId, CheckpointQueryFragment, CheckpointTotalTxQueryFragment,
18 CheckpointsArgs, CheckpointsQueryFragment,
19 },
20 streams::stream_paginated_query,
21};
22
23define_query! {
24 #[derive(Clone)]
26 pub struct ListCheckpointsQuery {
27 client: GraphQLClient,
28 pagination: PaginationFilter,
29 }
30 output: GraphQLResult<Page<CheckpointSummary>>;
31}
32
33impl ListCheckpointsQuery {
34 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
36 self.pagination = pagination;
37 self
38 }
39
40 pub fn stream(self) -> impl Stream<Item = GraphQLResult<CheckpointSummary>> + Unpin {
45 let pagination = self.pagination.clone();
46 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
47 }
48
49 fn operation<'a>(
50 pagination: &'a PaginationFilterResponse,
51 ) -> cynic::Operation<CheckpointsQueryFragment, CheckpointsArgs<'a>> {
52 CheckpointsQueryFragment::build(CheckpointsArgs {
53 after: pagination.after.as_deref(),
54 before: pagination.before.as_deref(),
55 first: pagination.first,
56 last: pagination.last,
57 })
58 }
59
60 async fn send(self) -> GraphQLResult<Page<CheckpointSummary>> {
61 let Self { client, pagination } = self;
62 let pagination = client.pagination_filter(pagination).await;
63 let response = client.run_query(&Self::operation(&pagination)).await?;
64
65 let cc = response.checkpoints;
66 let page_info = cc.page_info;
67 let nodes = cc
68 .nodes
69 .into_iter()
70 .map(|c| c.try_into())
71 .collect::<GraphQLResult<Vec<_>>>()?;
72
73 Ok(Page::new(page_info, nodes))
74 }
75}
76
77define_query! {
78 pub struct GetCheckpointQuery {
83 client: GraphQLClient,
84 digest: Option<CheckpointDigest>,
85 sequence_number: Option<u64>,
86 }
87 output: GraphQLResult<Option<CheckpointSummary>>;
88}
89
90impl GetCheckpointQuery {
91 async fn send(self) -> GraphQLResult<Option<CheckpointSummary>> {
92 let operation = CheckpointQueryFragment::build(CheckpointArgs {
93 id: CheckpointId {
94 digest: self.digest.map(|d| d.to_string()),
95 sequence_number: self.sequence_number,
96 },
97 });
98 let response = self.client.run_query(&operation).await?;
99
100 response.checkpoint.map(|c| c.try_into()).transpose()
101 }
102}
103
104define_query! {
105 pub struct GetLatestCheckpointSequenceNumberQuery {
108 client: GraphQLClient,
109 }
110 output: GraphQLResult<Option<CheckpointSequenceNumber>>;
111}
112
113impl GetLatestCheckpointSequenceNumberQuery {
114 async fn send(self) -> GraphQLResult<Option<CheckpointSequenceNumber>> {
115 Ok(self.client.checkpoint().await?.map(|c| c.sequence_number))
116 }
117}
118
119define_query! {
120 pub struct GetTotalTransactionBlocksQuery {
125 client: GraphQLClient,
126 digest: Option<String>,
127 sequence_number: Option<u64>,
128 }
129 output: GraphQLResult<Option<u64>>;
130}
131
132impl GetTotalTransactionBlocksQuery {
133 async fn send(self) -> GraphQLResult<Option<u64>> {
134 let operation = CheckpointTotalTxQueryFragment::build(CheckpointArgs {
135 id: CheckpointId {
136 digest: self.digest,
137 sequence_number: self.sequence_number,
138 },
139 });
140 let response = self.client.run_query(&operation).await?;
141
142 Ok(response
143 .checkpoint
144 .and_then(|c| c.network_total_transactions))
145 }
146}
147
148impl GraphQLClient {
149 pub fn checkpoint(&self) -> GetCheckpointQuery {
151 GetCheckpointQuery {
152 client: self.clone(),
153 digest: None,
154 sequence_number: None,
155 }
156 }
157
158 pub fn checkpoint_by_digest(&self, digest: CheckpointDigest) -> GetCheckpointQuery {
160 GetCheckpointQuery {
161 client: self.clone(),
162 digest: Some(digest),
163 sequence_number: None,
164 }
165 }
166
167 pub fn checkpoint_by_sequence_number(&self, sequence_number: u64) -> GetCheckpointQuery {
169 GetCheckpointQuery {
170 client: self.clone(),
171 digest: None,
172 sequence_number: Some(sequence_number),
173 }
174 }
175
176 pub fn checkpoints(&self) -> ListCheckpointsQuery {
178 ListCheckpointsQuery {
179 client: self.clone(),
180 pagination: PaginationFilter::default(),
181 }
182 }
183
184 pub fn latest_checkpoint_sequence_number(&self) -> GetLatestCheckpointSequenceNumberQuery {
187 GetLatestCheckpointSequenceNumberQuery {
188 client: self.clone(),
189 }
190 }
191
192 pub fn total_transaction_blocks_by_digest(
195 &self,
196 digest: CheckpointDigest,
197 ) -> GetTotalTransactionBlocksQuery {
198 GetTotalTransactionBlocksQuery {
199 client: self.clone(),
200 digest: Some(digest.to_string()),
201 sequence_number: None,
202 }
203 }
204
205 pub fn total_transaction_blocks_by_sequence_number(
208 &self,
209 sequence_number: u64,
210 ) -> GetTotalTransactionBlocksQuery {
211 GetTotalTransactionBlocksQuery {
212 client: self.clone(),
213 digest: None,
214 sequence_number: Some(sequence_number),
215 }
216 }
217
218 pub fn total_transaction_blocks(&self) -> GetTotalTransactionBlocksQuery {
221 GetTotalTransactionBlocksQuery {
222 client: self.clone(),
223 digest: None,
224 sequence_number: None,
225 }
226 }
227}
228
229#[cfg(all(test, not(target_arch = "wasm32")))]
230mod tests {
231 use iota_types::CheckpointDigest;
232
233 use crate::test_utils::{
234 assert_backward_page, assert_forward_page, backward_page, forward_page, sent_variables,
235 test_client,
236 };
237
238 #[tokio::test]
239 async fn total_transaction_blocks_send_the_checkpoint() {
240 let request = sent_variables("CheckpointTotalTxQueryFragment", |client| async move {
241 let _ = client.total_transaction_blocks_by_sequence_number(7).await;
242 })
243 .await;
244 let vars = &request;
245 assert_eq!(vars["id"]["sequenceNumber"], 7);
246 assert!(vars["id"]["digest"].is_null());
247
248 let digest = CheckpointDigest::ZERO;
249 let vars = sent_variables("CheckpointTotalTxQueryFragment", |client| async move {
250 let _ = client.total_transaction_blocks_by_digest(digest).await;
251 })
252 .await;
253 assert_eq!(vars["id"]["digest"], digest.to_string());
254 assert!(vars["id"]["sequenceNumber"].is_null());
255
256 let request = sent_variables("CheckpointTotalTxQueryFragment", |client| async move {
257 let _ = client.total_transaction_blocks().await;
258 })
259 .await;
260 assert!(request["id"]["digest"].is_null());
261 assert!(request["id"]["sequenceNumber"].is_null());
262
263 let request = sent_variables("CheckpointQueryFragment", |client| async move {
264 let _ = client.latest_checkpoint_sequence_number().await;
265 })
266 .await;
267 assert!(request["id"]["digest"].is_null());
268 assert!(request["id"]["sequenceNumber"].is_null());
269 }
270
271 #[tokio::test]
272 async fn checkpoint_sends_the_digest_or_sequence_number() {
273 let vars = sent_variables("CheckpointQueryFragment", |client| async move {
274 let _ = client.checkpoint_by_sequence_number(7).await;
275 })
276 .await;
277 assert_eq!(vars["id"]["sequenceNumber"], 7);
278 assert!(vars["id"]["digest"].is_null());
279
280 let digest = CheckpointDigest::ZERO;
281 let vars = sent_variables("CheckpointQueryFragment", |client| async move {
282 let _ = client.checkpoint_by_digest(digest).await;
283 })
284 .await;
285 assert_eq!(vars["id"]["digest"], digest.to_string());
286 assert!(vars["id"]["sequenceNumber"].is_null());
287
288 let vars = sent_variables("CheckpointQueryFragment", |client| async move {
289 let _ = client.checkpoint().await;
290 })
291 .await;
292 assert!(vars["id"]["digest"].is_null());
293 assert!(vars["id"]["sequenceNumber"].is_null());
294 }
295
296 #[tokio::test]
297 async fn checkpoints_sends_the_pagination() {
298 let vars = sent_variables("CheckpointsQueryFragment", |client| async move {
299 let _ = client.checkpoints().pagination(backward_page()).await;
300 })
301 .await;
302 assert_backward_page(&vars);
303
304 let vars = sent_variables("CheckpointsQueryFragment", |client| async move {
305 let _ = client.checkpoints().pagination(forward_page()).await;
306 })
307 .await;
308 assert_forward_page(&vars);
309 }
310
311 #[test]
312 fn checkpoints_query_forwards_pagination_arguments() {
313 use cynic::QueryBuilder;
314
315 use crate::query_types::{CheckpointsArgs, CheckpointsQueryFragment};
316
317 let operation = CheckpointsQueryFragment::build(CheckpointsArgs {
318 first: Some(10),
319 after: None,
320 last: None,
321 before: None,
322 });
323 for arg in ["first:", "after:", "last:", "before:"] {
324 assert!(
325 operation.query.contains(arg),
326 "checkpoints query is missing the `{arg}` argument:\n{}",
327 operation.query
328 );
329 }
330 }
331
332 #[tokio::test]
333 async fn test_checkpoint_query() {
334 let client = test_client();
335 client
336 .checkpoint()
337 .await
338 .map_err(|e| {
339 format!(
340 "Checkpoint query failed for {} network: Error: {e}",
341 client.rpc_server()
342 )
343 })
344 .unwrap()
345 .unwrap();
346 }
347
348 #[tokio::test]
349 async fn test_checkpoints_query() {
350 let client = test_client();
351 let cs = client
352 .checkpoints()
353 .await
354 .map_err(|e| {
355 format!(
356 "Checkpoints query failed for {} network: Error {e}",
357 client.rpc_server()
358 )
359 })
360 .unwrap();
361
362 assert!(
363 !cs.is_empty(),
364 "Checkpoints query returned no data for {} network",
365 client.rpc_server()
366 );
367 }
368
369 #[tokio::test]
370 async fn test_latest_checkpoint_sequence_number_query() {
371 let client = test_client();
372 client
373 .latest_checkpoint_sequence_number()
374 .await
375 .map_err(|e| {
376 format!(
377 "Latest checkpoint sequence number query failed for {} network: Error {e}",
378 client.rpc_server()
379 )
380 })
381 .unwrap()
382 .unwrap();
383 }
384
385 #[tokio::test]
386 async fn test_total_transaction_blocks() {
387 let client = test_client();
388 let total_transaction_blocks = client
389 .total_transaction_blocks()
390 .await
391 .map_err(|e| {
392 format!(
393 "Total transaction blocks query failed for {} network. Error: {e}",
394 client.rpc_server()
395 )
396 })
397 .unwrap()
398 .unwrap();
399 assert!(total_transaction_blocks > 0);
400
401 let checkpoint_sequence_number = client
402 .latest_checkpoint_sequence_number()
403 .await
404 .map_err(|e| {
405 format!(
406 "Latest checkpoint sequence number query failed for {} network. Error: {e}",
407 client.rpc_server()
408 )
409 })
410 .unwrap()
411 .unwrap();
412 let total_transaction_blocks_by_sequence_number = client
413 .total_transaction_blocks_by_sequence_number(checkpoint_sequence_number)
414 .await
415 .unwrap()
416 .unwrap();
417 assert!(
418 total_transaction_blocks_by_sequence_number >= total_transaction_blocks,
419 "expected at least {total_transaction_blocks} transaction blocks, found {total_transaction_blocks_by_sequence_number}"
420 );
421
422 let checkpoint = client
423 .checkpoint_by_sequence_number(checkpoint_sequence_number)
424 .await
425 .unwrap()
426 .unwrap();
427
428 let total_transaction_blocks_by_digest = client
429 .total_transaction_blocks_by_digest(checkpoint.digest())
430 .await
431 .unwrap()
432 .unwrap();
433 assert_eq!(
434 total_transaction_blocks_by_sequence_number,
435 total_transaction_blocks_by_digest
436 );
437 }
438}