Skip to main content

sui_indexer_alt_jsonrpc/api/transactions/
response.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::str::FromStr;
5use std::sync::Arc;
6
7use anyhow::Context as _;
8use move_core_types::annotated_value::MoveDatatypeLayout;
9use move_core_types::annotated_value::MoveTypeLayout;
10use sui_indexer_alt_reader::kv_loader::TransactionContents;
11use sui_indexer_alt_reader::objects::VersionedObjectKey;
12use sui_json_rpc_types::BalanceChange as SuiBalanceChange;
13use sui_json_rpc_types::ObjectChange as SuiObjectChange;
14use sui_json_rpc_types::SuiEvent;
15use sui_json_rpc_types::SuiTransactionBlock;
16use sui_json_rpc_types::SuiTransactionBlockData;
17use sui_json_rpc_types::SuiTransactionBlockEffects;
18use sui_json_rpc_types::SuiTransactionBlockEvents;
19use sui_json_rpc_types::SuiTransactionBlockResponse;
20use sui_json_rpc_types::SuiTransactionBlockResponseOptions;
21use sui_types::TypeTag;
22use sui_types::base_types::ObjectID;
23use sui_types::base_types::SequenceNumber;
24use sui_types::base_types::SuiAddress;
25use sui_types::digests::ObjectDigest;
26use sui_types::digests::TransactionDigest;
27use sui_types::effects::ObjectChange;
28use sui_types::effects::TransactionEffects;
29use sui_types::effects::TransactionEffectsAPI;
30use sui_types::object::Object;
31use sui_types::object::Owner;
32use sui_types::signature::GenericSignature;
33use sui_types::transaction::SenderSignedData;
34use sui_types::transaction::TransactionData;
35use sui_types::transaction::TransactionDataAPI;
36
37use crate::api::to_sui_object_change;
38use crate::api::transactions::error::Error;
39use crate::context::Context;
40use crate::error::RpcError;
41use crate::error::invalid_params;
42use crate::error::rpc_bail;
43
44/// Fetch the necessary data from the stores in `ctx` and transform it to build a response for the
45/// transaction identified by `digest`, according to the response `options`.
46pub(super) async fn transaction(
47    ctx: &Context,
48    digest: TransactionDigest,
49    options: &SuiTransactionBlockResponseOptions,
50) -> Result<SuiTransactionBlockResponse, RpcError<Error>> {
51    let tx = ctx
52        .kv_loader()
53        .load_one_transaction(digest)
54        .await
55        .context("Failed to fetch transaction from store")?
56        .ok_or_else(|| invalid_params(Error::NotFound(digest)))?;
57
58    let digest = tx.digest()?;
59
60    let mut response = SuiTransactionBlockResponse::new(digest);
61
62    response.timestamp_ms = tx.timestamp_ms();
63    response.checkpoint = tx.cp_sequence_number();
64
65    if options.show_input {
66        response.transaction = Some(input(ctx, &tx).await?);
67    }
68
69    if options.show_raw_input {
70        response.raw_transaction = raw_input(&tx)?;
71    }
72
73    if options.show_effects {
74        response.effects = Some(effects(&tx)?);
75    }
76
77    if options.show_raw_effects {
78        response.raw_effects = tx.raw_effects()?;
79    }
80
81    if options.show_events {
82        response.events = Some(events(ctx, digest, &tx).await?);
83    }
84
85    if options.show_balance_changes {
86        response.balance_changes = Some(balance_changes(&tx)?);
87    }
88
89    if options.show_object_changes {
90        response.object_changes = Some(object_changes(ctx, digest, &tx).await?);
91    }
92
93    Ok(response)
94}
95
96/// Extract a representation of the transaction's input data from the stored form.
97async fn input(
98    ctx: &Context,
99    tx: &TransactionContents,
100) -> Result<SuiTransactionBlock, RpcError<Error>> {
101    let data: TransactionData = tx.data()?;
102    let tx_signatures: Vec<GenericSignature> = tx.signatures()?;
103
104    Ok(SuiTransactionBlock {
105        data: SuiTransactionBlockData::try_from_with_package_resolver(data, ctx.package_resolver())
106            .await
107            .context("Failed to resolve types in transaction data")?,
108        tx_signatures,
109    })
110}
111
112/// Extract the transaction's raw BCS input in the JSON-RPC representation.
113fn raw_input(tx: &TransactionContents) -> Result<Vec<u8>, RpcError<Error>> {
114    let data = SenderSignedData::new(tx.data()?, tx.signatures()?);
115    Ok(bcs::to_bytes(&data).context("Failed to serialize transaction")?)
116}
117
118/// Extract a representation of the transaction's effects from the stored form.
119fn effects(tx: &TransactionContents) -> Result<SuiTransactionBlockEffects, RpcError<Error>> {
120    let effects: TransactionEffects = tx.effects()?;
121    Ok(effects
122        .try_into()
123        .context("Failed to convert Effects into response")?)
124}
125
126/// Extract the transaction's events from its stored form.
127async fn events(
128    ctx: &Context,
129    digest: TransactionDigest,
130    tx: &TransactionContents,
131) -> Result<SuiTransactionBlockEvents, RpcError<Error>> {
132    let events = tx.events()?;
133    let mut sui_events = Vec::with_capacity(events.len());
134
135    for (ix, event) in events.into_iter().enumerate() {
136        let layout = match ctx
137            .package_resolver()
138            .type_layout(event.type_.clone().into())
139            .await
140            .with_context(|| {
141                format!(
142                    "Failed to resolve layout for {}",
143                    event.type_.to_canonical_display(/* with_prefix */ true)
144                )
145            })? {
146            MoveTypeLayout::Struct(s) => MoveDatatypeLayout::Struct(s),
147            MoveTypeLayout::Enum(e) => MoveDatatypeLayout::Enum(e),
148            _ => rpc_bail!(
149                "Event {ix} is not a struct or enum: {}",
150                event.type_.to_canonical_string(/* with_prefix */ true)
151            ),
152        };
153
154        let sui_event = SuiEvent::try_from(
155            Arc::unwrap_or_clone(event),
156            digest,
157            ix as u64,
158            tx.timestamp_ms(),
159            layout,
160        )
161        .with_context(|| format!("Failed to convert Event {ix} into response"))?;
162
163        sui_events.push(sui_event)
164    }
165
166    Ok(SuiTransactionBlockEvents { data: sui_events })
167}
168
169/// Extract the transaction's balance changes from the ledger gRPC response.
170fn balance_changes(tx: &TransactionContents) -> Result<Vec<SuiBalanceChange>, RpcError<Error>> {
171    let balance_changes = tx.balance_changes();
172    let mut response = Vec::with_capacity(balance_changes.len());
173
174    for bc in balance_changes {
175        let addr: SuiAddress = bc
176            .address
177            .as_ref()
178            .context("Missing address in balance change")?
179            .parse()
180            .context("Invalid owner address in balance change")?;
181
182        let coin_type = TypeTag::from_str(
183            bc.coin_type
184                .as_ref()
185                .context("Missing coin_type in balance change")?,
186        )
187        .context("Invalid coin type in balance change")?;
188
189        let amount: i128 = bc
190            .amount
191            .as_ref()
192            .context("Missing amount in balance change")?
193            .parse()
194            .context("Invalid balance change amount")?;
195
196        response.push(SuiBalanceChange {
197            owner: Owner::AddressOwner(addr),
198            coin_type,
199            amount,
200        });
201    }
202
203    Ok(response)
204}
205
206/// Extract the transaction's object changes. Object IDs and versions are fetched from the stored
207/// transaction, and the object contents are fetched separately by a data loader.
208async fn object_changes(
209    ctx: &Context,
210    digest: TransactionDigest,
211    tx: &TransactionContents,
212) -> Result<Vec<SuiObjectChange>, RpcError<Error>> {
213    let tx_data: TransactionData = tx.data()?;
214    let effects: TransactionEffects = tx.effects()?;
215
216    let mut keys = vec![];
217    let native_changes = effects.object_changes();
218    for change in &native_changes {
219        let id = change.id;
220        if let Some(version) = change.input_version {
221            keys.push(VersionedObjectKey(id, version.value()));
222        }
223        if let Some(version) = change.output_version {
224            keys.push(VersionedObjectKey(id, version.value()));
225        }
226    }
227
228    let objects = ctx
229        .kv_loader()
230        .load_many_objects(keys)
231        .await
232        .context("Failed to fetch object contents")?;
233
234    // Fetch and deserialize the contents of an object, based on its object ref. Assumes that all
235    // object versions that will be fetched in this way have come from a valid transaction, and
236    // have been passed to the data loader in the call above. This means that if they cannot be
237    // found, they must have been pruned.
238    let fetch_object = |id: ObjectID,
239                        v: Option<SequenceNumber>,
240                        d: Option<ObjectDigest>|
241     -> Result<Option<(Object, ObjectDigest)>, RpcError<Error>> {
242        let Some(v) = v else { return Ok(None) };
243        let Some(d) = d else { return Ok(None) };
244
245        let v = v.value();
246
247        let o = objects
248            .get(&VersionedObjectKey(id, v))
249            .ok_or_else(|| invalid_params(Error::PrunedObject(digest, id, v)))?;
250
251        Ok(Some((o.clone(), d)))
252    };
253
254    let mut changes = Vec::with_capacity(native_changes.len());
255
256    for change in native_changes {
257        let &ObjectChange {
258            id: object_id,
259            id_operation,
260            input_version,
261            input_digest,
262            output_version,
263            output_digest,
264            ..
265        } = &change;
266
267        let input = fetch_object(object_id, input_version, input_digest)?;
268        let output = fetch_object(object_id, output_version, output_digest)?;
269
270        changes.extend(to_sui_object_change(
271            tx_data.sender(),
272            object_id,
273            id_operation,
274            input,
275            output,
276            effects.lamport_version(),
277        )?);
278    }
279
280    Ok(changes)
281}