sui_indexer_alt_jsonrpc/api/transactions/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use futures::future;
5use jsonrpsee::{core::RpcResult, proc_macros::rpc};
6use sui_json_rpc_types::{Page, SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions};
7use sui_open_rpc::Module;
8use sui_open_rpc_macros::open_rpc;
9use sui_types::digests::TransactionDigest;
10
11use self::{error::Error, filter::SuiTransactionBlockResponseQuery};
12
13use crate::{
14    context::Context,
15    error::{InternalContext, RpcError, rpc_bail},
16};
17
18use super::rpc_module::RpcModule;
19
20mod error;
21mod filter;
22mod response;
23
24#[open_rpc(namespace = "sui", tag = "Transactions API")]
25#[rpc(server, namespace = "sui")]
26trait TransactionsApi {
27    /// Fetch a transaction by its transaction digest.
28    #[method(name = "getTransactionBlock")]
29    async fn get_transaction_block(
30        &self,
31        /// The digest of the queried transaction.
32        digest: TransactionDigest,
33        /// Options controlling the output format.
34        options: Option<SuiTransactionBlockResponseOptions>,
35    ) -> RpcResult<SuiTransactionBlockResponse>;
36}
37
38#[open_rpc(namespace = "suix", tag = "Query Transactions API")]
39#[rpc(server, namespace = "suix")]
40trait QueryTransactionsApi {
41    /// Query transactions based on their properties (sender, affected addresses, function calls,
42    /// etc). Returns a paginated list of transactions.
43    ///
44    /// If a cursor is provided, the query will start from the transaction after the one pointed to
45    /// by this cursor, otherwise pagination starts from the first transaction that meets the query
46    /// criteria.
47    ///
48    /// The definition of "first" transaction is changed by the `descending_order` parameter, which
49    /// is optional, and defaults to false, meaning that the oldest transaction is shown first.
50    ///
51    /// The size of each page is controlled by the `limit` parameter.
52    #[method(name = "queryTransactionBlocks")]
53    async fn query_transaction_blocks(
54        &self,
55        /// The query criteria, and the output options.
56        query: SuiTransactionBlockResponseQuery,
57        /// Cursor to start paginating from.
58        cursor: Option<String>,
59        /// Maximum number of transactions to return per page.
60        limit: Option<usize>,
61        /// Order of results, defaulting to ascending order (false), by sequence on-chain.
62        descending_order: Option<bool>,
63    ) -> RpcResult<Page<SuiTransactionBlockResponse, String>>;
64}
65
66pub(crate) struct Transactions(pub Context);
67
68pub(crate) struct QueryTransactions(pub Context);
69
70#[async_trait::async_trait]
71impl TransactionsApiServer for Transactions {
72    async fn get_transaction_block(
73        &self,
74        digest: TransactionDigest,
75        options: Option<SuiTransactionBlockResponseOptions>,
76    ) -> RpcResult<SuiTransactionBlockResponse> {
77        let Self(ctx) = self;
78        Ok(
79            response::transaction(ctx, digest, &options.unwrap_or_default())
80                .await
81                .with_internal_context(|| format!("Failed to get transaction {digest}"))?,
82        )
83    }
84}
85
86#[async_trait::async_trait]
87impl QueryTransactionsApiServer for QueryTransactions {
88    async fn query_transaction_blocks(
89        &self,
90        query: SuiTransactionBlockResponseQuery,
91        cursor: Option<String>,
92        limit: Option<usize>,
93        descending_order: Option<bool>,
94    ) -> RpcResult<Page<SuiTransactionBlockResponse, String>> {
95        let Self(ctx) = self;
96
97        let Page {
98            data: digests,
99            next_cursor,
100            has_next_page,
101        } = filter::transactions(ctx, &query.filter, cursor.clone(), limit, descending_order)
102            .await?;
103
104        let options = query.options.unwrap_or_default();
105
106        let tx_futures = digests.iter().map(|d| {
107            async {
108                let mut tx = response::transaction(ctx, *d, &options).await;
109
110                let config = &ctx.config().transactions;
111                let mut interval = tokio::time::interval(std::time::Duration::from_millis(
112                    config.tx_retry_interval_ms,
113                ));
114
115                let mut retries = 0;
116                for _ in 0..config.tx_retry_count {
117                    // Retry only if the error is an invalid params error, which can only be due to
118                    // the transaction not being found in the kv store or tx balance changes table.
119                    if let Err(RpcError::InvalidParams(
120                        _e @ (Error::BalanceChangesNotFound(_) | Error::NotFound(_)),
121                    )) = tx
122                    {
123                        interval.tick().await;
124                        retries += 1;
125                        tx = response::transaction(ctx, *d, &options).await;
126                        ctx.metrics()
127                            .read_retries
128                            .with_label_values(&["tx_response"])
129                            .inc();
130                    } else {
131                        break;
132                    }
133                }
134
135                ctx.metrics()
136                    .read_retries_per_request
137                    .with_label_values(&["tx_response"])
138                    .observe(retries as f64);
139                tx
140            }
141        });
142
143        let data = future::join_all(tx_futures)
144            .await
145            .into_iter()
146            .zip(digests)
147            .map(|(r, d)| {
148                if let Err(RpcError::InvalidParams(e @ Error::NotFound(_))) = r {
149                    rpc_bail!(e)
150                } else {
151                    r.with_internal_context(|| format!("Failed to get transaction {d}"))
152                }
153            })
154            .collect::<Result<Vec<_>, _>>()?;
155
156        Ok(Page {
157            data,
158            next_cursor: next_cursor.or(cursor),
159            has_next_page,
160        })
161    }
162}
163
164impl RpcModule for Transactions {
165    fn schema(&self) -> Module {
166        TransactionsApiOpenRpc::module_doc()
167    }
168
169    fn into_impl(self) -> jsonrpsee::RpcModule<Self> {
170        self.into_rpc()
171    }
172}
173
174impl RpcModule for QueryTransactions {
175    fn schema(&self) -> Module {
176        QueryTransactionsApiOpenRpc::module_doc()
177    }
178
179    fn into_impl(self) -> jsonrpsee::RpcModule<Self> {
180        self.into_rpc()
181    }
182}