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