Skip to main content

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;
6use jsonrpsee::proc_macros::rpc;
7use mysten_common::ZipDebugEqIteratorExt;
8use sui_json_rpc_types::Page;
9use sui_json_rpc_types::SuiTransactionBlockResponse;
10use sui_json_rpc_types::SuiTransactionBlockResponseOptions;
11use sui_open_rpc::Module;
12use sui_open_rpc_macros::open_rpc;
13use sui_types::digests::TransactionDigest;
14
15use crate::api::rpc_module::RpcModule;
16use crate::api::transactions::error::Error;
17use crate::api::transactions::filter::SuiTransactionBlockResponseQuery;
18use crate::context::Context;
19use crate::error::InternalContext;
20use crate::error::RpcError;
21use crate::error::rpc_bail;
22
23mod error;
24mod filter;
25mod response;
26
27#[open_rpc(namespace = "sui", tag = "Transactions API")]
28#[rpc(server, namespace = "sui")]
29trait TransactionsApi {
30    /// Fetch a transaction by its transaction digest.
31    #[method(name = "getTransactionBlock")]
32    async fn get_transaction_block(
33        &self,
34        /// The digest of the queried transaction.
35        digest: TransactionDigest,
36        /// Options controlling the output format.
37        options: Option<SuiTransactionBlockResponseOptions>,
38    ) -> RpcResult<SuiTransactionBlockResponse>;
39}
40
41#[open_rpc(namespace = "suix", tag = "Query Transactions API")]
42#[rpc(server, namespace = "suix")]
43trait QueryTransactionsApi {
44    /// Query transactions based on their properties (sender, affected addresses, function calls,
45    /// etc). Returns a paginated list of transactions.
46    ///
47    /// If a cursor is provided, the query will start from the transaction after the one pointed to
48    /// by this cursor, otherwise pagination starts from the first transaction that meets the query
49    /// criteria.
50    ///
51    /// The definition of "first" transaction is changed by the `descending_order` parameter, which
52    /// is optional, and defaults to false, meaning that the oldest transaction is shown first.
53    ///
54    /// The size of each page is controlled by the `limit` parameter.
55    #[method(name = "queryTransactionBlocks")]
56    async fn query_transaction_blocks(
57        &self,
58        /// The query criteria, and the output options.
59        query: SuiTransactionBlockResponseQuery,
60        /// Cursor to start paginating from.
61        cursor: Option<String>,
62        /// Maximum number of transactions to return per page.
63        limit: Option<usize>,
64        /// Order of results, defaulting to ascending order (false), by sequence on-chain.
65        descending_order: Option<bool>,
66    ) -> RpcResult<Page<SuiTransactionBlockResponse, String>>;
67}
68
69pub(crate) struct Transactions(pub Context);
70
71pub(crate) struct QueryTransactions(pub Context);
72
73#[async_trait::async_trait]
74impl TransactionsApiServer for Transactions {
75    async fn get_transaction_block(
76        &self,
77        digest: TransactionDigest,
78        options: Option<SuiTransactionBlockResponseOptions>,
79    ) -> RpcResult<SuiTransactionBlockResponse> {
80        let Self(ctx) = self;
81        Ok(
82            response::transaction(ctx, digest, &options.unwrap_or_default())
83                .await
84                .with_internal_context(|| format!("Failed to get transaction {digest}"))?,
85        )
86    }
87}
88
89#[async_trait::async_trait]
90impl QueryTransactionsApiServer for QueryTransactions {
91    async fn query_transaction_blocks(
92        &self,
93        query: SuiTransactionBlockResponseQuery,
94        cursor: Option<String>,
95        limit: Option<usize>,
96        descending_order: Option<bool>,
97    ) -> RpcResult<Page<SuiTransactionBlockResponse, String>> {
98        let Self(ctx) = self;
99
100        let Page {
101            data: digests,
102            next_cursor,
103            has_next_page,
104        } = filter::transactions(ctx, &query.filter, cursor.clone(), limit, descending_order)
105            .await?;
106
107        let options = query.options.unwrap_or_default();
108
109        let tx_futures = digests.iter().map(|d| {
110            async {
111                let mut tx = response::transaction(ctx, *d, &options).await;
112
113                let config = &ctx.config().transactions;
114                let mut interval = tokio::time::interval(std::time::Duration::from_millis(
115                    config.tx_retry_interval_ms,
116                ));
117
118                let mut retries = 0;
119                for _ in 0..config.tx_retry_count {
120                    // Retry only if the error is an invalid params error, which can only be due to
121                    // the transaction not being found in the kv store.
122                    if let Err(RpcError::InvalidParams(_e @ Error::NotFound(_))) = tx {
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_debug_eq(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}