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