sui_indexer_alt_jsonrpc/api/transactions/
response.rs1use std::str::FromStr;
5use std::sync::Arc;
6
7use anyhow::Context as _;
8use futures::future::OptionFuture;
9use move_core_types::annotated_value::MoveDatatypeLayout;
10use move_core_types::annotated_value::MoveTypeLayout;
11use sui_indexer_alt_reader::kv_loader::TransactionContents;
12use sui_indexer_alt_reader::objects::VersionedObjectKey;
13use sui_indexer_alt_reader::tx_balance_changes::TxBalanceChangeKey;
14use sui_indexer_alt_schema::transactions::BalanceChange;
15use sui_indexer_alt_schema::transactions::StoredTxBalanceChange;
16use sui_json_rpc_types::BalanceChange as SuiBalanceChange;
17use sui_json_rpc_types::ObjectChange as SuiObjectChange;
18use sui_json_rpc_types::SuiEvent;
19use sui_json_rpc_types::SuiTransactionBlock;
20use sui_json_rpc_types::SuiTransactionBlockData;
21use sui_json_rpc_types::SuiTransactionBlockEffects;
22use sui_json_rpc_types::SuiTransactionBlockEvents;
23use sui_json_rpc_types::SuiTransactionBlockResponse;
24use sui_json_rpc_types::SuiTransactionBlockResponseOptions;
25use sui_types::TypeTag;
26use sui_types::base_types::ObjectID;
27use sui_types::base_types::SequenceNumber;
28use sui_types::digests::ObjectDigest;
29use sui_types::digests::TransactionDigest;
30use sui_types::effects::ObjectChange;
31use sui_types::effects::TransactionEffects;
32use sui_types::effects::TransactionEffectsAPI;
33use sui_types::object::Object;
34use sui_types::signature::GenericSignature;
35use sui_types::transaction::TransactionData;
36use sui_types::transaction::TransactionDataAPI;
37use tokio::join;
38
39use crate::api::to_sui_object_change;
40use crate::api::transactions::error::Error;
41use crate::context::Context;
42use crate::error::RpcError;
43use crate::error::invalid_params;
44use crate::error::rpc_bail;
45
46pub(super) async fn transaction(
49 ctx: &Context,
50 digest: TransactionDigest,
51 options: &SuiTransactionBlockResponseOptions,
52) -> Result<SuiTransactionBlockResponse, RpcError<Error>> {
53 let tx = ctx.kv_loader().load_one_transaction(digest);
54 let stored_bc: OptionFuture<_> = options
55 .show_balance_changes
56 .then(|| ctx.pg_loader().load_one(TxBalanceChangeKey(digest)))
57 .into();
58
59 let (tx, stored_bc) = join!(tx, stored_bc);
60
61 let tx = tx
62 .context("Failed to fetch transaction from store")?
63 .ok_or_else(|| invalid_params(Error::NotFound(digest)))?;
64
65 let stored_bc = match stored_bc
68 .transpose()
69 .context("Failed to fetch balance changes from store")?
70 {
71 Some(None) => return Err(invalid_params(Error::BalanceChangesNotFound(digest))),
72 Some(changes) => changes,
73 None => None,
74 };
75
76 let digest = tx.digest()?;
77
78 let mut response = SuiTransactionBlockResponse::new(digest);
79
80 response.timestamp_ms = tx.timestamp_ms();
81 response.checkpoint = tx.cp_sequence_number();
82
83 if options.show_input {
84 response.transaction = Some(input(ctx, &tx).await?);
85 }
86
87 if options.show_raw_input {
88 response.raw_transaction = tx.raw_transaction()?;
89 }
90
91 if options.show_effects {
92 response.effects = Some(effects(&tx)?);
93 }
94
95 if options.show_raw_effects {
96 response.raw_effects = tx.raw_effects()?;
97 }
98
99 if options.show_events {
100 response.events = Some(events(ctx, digest, &tx).await?);
101 }
102
103 if let Some(changes) = stored_bc {
104 response.balance_changes = Some(balance_changes(changes)?);
105 }
106
107 if options.show_object_changes {
108 response.object_changes = Some(object_changes(ctx, digest, &tx).await?);
109 }
110
111 Ok(response)
112}
113
114async fn input(
116 ctx: &Context,
117 tx: &TransactionContents,
118) -> Result<SuiTransactionBlock, RpcError<Error>> {
119 let data: TransactionData = tx.data()?;
120 let tx_signatures: Vec<GenericSignature> = tx.signatures()?;
121
122 Ok(SuiTransactionBlock {
123 data: SuiTransactionBlockData::try_from_with_package_resolver(data, ctx.package_resolver())
124 .await
125 .context("Failed to resolve types in transaction data")?,
126 tx_signatures,
127 })
128}
129
130fn effects(tx: &TransactionContents) -> Result<SuiTransactionBlockEffects, RpcError<Error>> {
132 let effects: TransactionEffects = tx.effects()?;
133 Ok(effects
134 .try_into()
135 .context("Failed to convert Effects into response")?)
136}
137
138async fn events(
140 ctx: &Context,
141 digest: TransactionDigest,
142 tx: &TransactionContents,
143) -> Result<SuiTransactionBlockEvents, RpcError<Error>> {
144 let events = tx.events()?;
145 let mut sui_events = Vec::with_capacity(events.len());
146
147 for (ix, event) in events.into_iter().enumerate() {
148 let layout = match ctx
149 .package_resolver()
150 .type_layout(event.type_.clone().into())
151 .await
152 .with_context(|| {
153 format!(
154 "Failed to resolve layout for {}",
155 event.type_.to_canonical_display(true)
156 )
157 })? {
158 MoveTypeLayout::Struct(s) => MoveDatatypeLayout::Struct(s),
159 MoveTypeLayout::Enum(e) => MoveDatatypeLayout::Enum(e),
160 _ => rpc_bail!(
161 "Event {ix} is not a struct or enum: {}",
162 event.type_.to_canonical_string(true)
163 ),
164 };
165
166 let sui_event = SuiEvent::try_from(
167 Arc::unwrap_or_clone(event),
168 digest,
169 ix as u64,
170 tx.timestamp_ms(),
171 layout,
172 )
173 .with_context(|| format!("Failed to convert Event {ix} into response"))?;
174
175 sui_events.push(sui_event)
176 }
177
178 Ok(SuiTransactionBlockEvents { data: sui_events })
179}
180
181fn balance_changes(
183 balance_changes: StoredTxBalanceChange,
184) -> Result<Vec<SuiBalanceChange>, RpcError<Error>> {
185 let balance_changes: Vec<BalanceChange> = bcs::from_bytes(&balance_changes.balance_changes)
186 .context("Failed to deserialize BalanceChanges")?;
187 let mut response = Vec::with_capacity(balance_changes.len());
188
189 for BalanceChange::V1 {
190 owner,
191 coin_type,
192 amount,
193 } in balance_changes
194 {
195 let coin_type = TypeTag::from_str(&coin_type)
196 .with_context(|| format!("Invalid coin type: {coin_type:?}"))?;
197
198 response.push(SuiBalanceChange {
199 owner,
200 coin_type,
201 amount,
202 });
203 }
204
205 Ok(response)
206}
207
208async fn object_changes(
211 ctx: &Context,
212 digest: TransactionDigest,
213 tx: &TransactionContents,
214) -> Result<Vec<SuiObjectChange>, RpcError<Error>> {
215 let tx_data: TransactionData = tx.data()?;
216 let effects: TransactionEffects = tx.effects()?;
217
218 let mut keys = vec![];
219 let native_changes = effects.object_changes();
220 for change in &native_changes {
221 let id = change.id;
222 if let Some(version) = change.input_version {
223 keys.push(VersionedObjectKey(id, version.value()));
224 }
225 if let Some(version) = change.output_version {
226 keys.push(VersionedObjectKey(id, version.value()));
227 }
228 }
229
230 let objects = ctx
231 .kv_loader()
232 .load_many_objects(keys)
233 .await
234 .context("Failed to fetch object contents")?;
235
236 let fetch_object = |id: ObjectID,
241 v: Option<SequenceNumber>,
242 d: Option<ObjectDigest>|
243 -> Result<Option<(Object, ObjectDigest)>, RpcError<Error>> {
244 let Some(v) = v else { return Ok(None) };
245 let Some(d) = d else { return Ok(None) };
246
247 let v = v.value();
248
249 let o = objects
250 .get(&VersionedObjectKey(id, v))
251 .ok_or_else(|| invalid_params(Error::PrunedObject(digest, id, v)))?;
252
253 Ok(Some((o.clone(), d)))
254 };
255
256 let mut changes = Vec::with_capacity(native_changes.len());
257
258 for change in native_changes {
259 let &ObjectChange {
260 id: object_id,
261 id_operation,
262 input_version,
263 input_digest,
264 output_version,
265 output_digest,
266 ..
267 } = &change;
268
269 let input = fetch_object(object_id, input_version, input_digest)?;
270 let output = fetch_object(object_id, output_version, output_digest)?;
271
272 changes.extend(to_sui_object_change(
273 tx_data.sender(),
274 object_id,
275 id_operation,
276 input,
277 output,
278 effects.lamport_version(),
279 )?);
280 }
281
282 Ok(changes)
283}