sui_gql_client/queries/
latest_version_at_checkpoint_v2.rs

1use af_sui_types::{Address, Version};
2use cynic::{QueryFragment, QueryVariables};
3
4use crate::{GraphQlClient, GraphQlErrors, GraphQlResponseExt as _, schema};
5
6#[derive(thiserror::Error, Clone, Debug, PartialEq)]
7pub enum Error<C: std::error::Error> {
8    #[error(
9        "The latest checkpoint number ({0}) known to the server is smaller than the queried \
10        checkpoint"
11    )]
12    WatermarkTooLow(u64),
13    #[error("Missing latest checkpoint number")]
14    NoLatestCheckpoint,
15    #[error("No data in GraphQL response")]
16    NoData,
17    #[error("No transaction blocks found for object {id}")]
18    NoTransactionBlocks { id: Address },
19    #[error("Missing transaction effects")]
20    MissingTxEffects,
21    #[error("In client: {0}")]
22    Client(C),
23    #[error("From server: {0}")]
24    Server(#[from] GraphQlErrors),
25}
26
27/// Get the object's highest version in the network history up to a checkpoint number.
28///
29/// IMPORTANT: this query uses the transactions table to retrieve the version of the
30/// requested object. In case the GQL server is not storing the transactions table, this
31/// query is not going to provide the expected results.
32///
33/// # Arguments
34/// - `client`: GQL client implementation
35/// - `id`: ID of the object
36/// - `ckpt_num`: highest checkpoint to consider; the server will scan the history `<= ckpt_num`
37pub async fn query<C: GraphQlClient>(
38    client: &C,
39    id: Address,
40    ckpt_num: u64,
41) -> Result<u64, Error<C::Error>> {
42    let Some(mut data): Option<Query> = client
43        .query(Variables {
44            object_id: Some(id),
45            checkpoint_num: Some(ckpt_num + 1),
46        })
47        .await
48        .map_err(Error::Client)?
49        .try_into_data()?
50    else {
51        return Err(Error::NoData);
52    };
53
54    let watermark = data
55        .checkpoint
56        .ok_or(Error::NoLatestCheckpoint)?
57        .sequence_number;
58    if watermark < ckpt_num {
59        return Err(Error::WatermarkTooLow(watermark));
60    }
61
62    Ok(data
63        .transaction_blocks
64        .nodes
65        .pop()
66        .ok_or_else(|| Error::NoTransactionBlocks { id })?
67        .effects
68        .ok_or(Error::MissingTxEffects)?
69        .lamport_version)
70}
71
72#[derive(QueryVariables, Debug)]
73struct Variables {
74    checkpoint_num: Option<Version>,
75    object_id: Option<Address>,
76}
77
78#[derive(QueryFragment, Debug)]
79#[cynic(graphql_type = "Query", variables = "Variables")]
80struct Query {
81    checkpoint: Option<CheckpointNumber>,
82
83    #[arguments(last: 1, filter: { beforeCheckpoint: $checkpoint_num, changedObject: $object_id })]
84    transaction_blocks: TransactionBlockConnection,
85}
86
87#[derive(QueryFragment, Debug)]
88#[cynic(graphql_type = "Checkpoint")]
89struct CheckpointNumber {
90    sequence_number: Version,
91}
92
93#[derive(QueryFragment, Debug)]
94struct TransactionBlockConnection {
95    nodes: Vec<TransactionBlock>,
96}
97
98#[derive(QueryFragment, Debug)]
99struct TransactionBlock {
100    effects: Option<TransactionBlockEffects>,
101}
102
103#[derive(QueryFragment, Debug)]
104struct TransactionBlockEffects {
105    lamport_version: Version,
106}
107
108#[cfg(test)]
109#[allow(clippy::unwrap_used)]
110#[test]
111fn init_gql_output() {
112    use cynic::QueryBuilder as _;
113    let vars = Variables {
114        object_id: Some(
115            "0x4264c07a42f9d002c1244e43a1f0fa21c49e4a25c7202c597b8476ef6bb57113"
116                .parse()
117                .unwrap(),
118        ),
119        checkpoint_num: Some(54773328),
120    };
121    let operation = Query::build(vars);
122    insta::assert_snapshot!(operation.query, @r###"
123    query Query($checkpointNum: UInt53, $objectId: SuiAddress) {
124      checkpoint {
125        sequenceNumber
126      }
127      transactionBlocks(last: 1, filter: {beforeCheckpoint: $checkpointNum, changedObject: $objectId}) {
128        nodes {
129          effects {
130            lamportVersion
131          }
132        }
133      }
134    }
135    "###);
136}