sui_json_rpc/
transaction_execution_api.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5use std::time::Duration;
6
7use async_trait::async_trait;
8use fastcrypto::encoding::Base64;
9use fastcrypto::traits::ToFromBytes;
10use jsonrpsee::RpcModule;
11use jsonrpsee::core::RpcResult;
12
13use crate::authority_state::StateRead;
14use crate::error::{Error, SuiRpcInputError};
15use crate::{
16    ObjectProviderCache, SuiRpcModule, get_balance_changes_from_effect, get_object_changes,
17    with_tracing,
18};
19use shared_crypto::intent::{AppId, Intent, IntentMessage, IntentScope, IntentVersion};
20use sui_core::authority::AuthorityState;
21use sui_core::authority_client::NetworkAuthorityClient;
22use sui_core::transaction_orchestrator::TransactionOrchestrator;
23use sui_json_rpc_api::{JsonRpcMetrics, WriteApiOpenRpc, WriteApiServer};
24use sui_json_rpc_types::{
25    DevInspectArgs, DevInspectResults, DryRunTransactionBlockResponse, SuiTransactionBlock,
26    SuiTransactionBlockEvents, SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions,
27};
28use sui_open_rpc::Module;
29use sui_types::base_types::SuiAddress;
30use sui_types::crypto::default_hash;
31use sui_types::digests::TransactionDigest;
32use sui_types::effects::TransactionEffectsAPI;
33use sui_types::quorum_driver_types::{
34    ExecuteTransactionRequestType, ExecuteTransactionRequestV3, ExecuteTransactionResponseV3,
35};
36use sui_types::signature::GenericSignature;
37use sui_types::storage::PostExecutionPackageResolver;
38use sui_types::sui_serde::BigInt;
39use sui_types::transaction::{
40    InputObjectKind, Transaction, TransactionData, TransactionDataAPI, TransactionKind,
41};
42use tracing::instrument;
43
44pub struct TransactionExecutionApi {
45    state: Arc<dyn StateRead>,
46    transaction_orchestrator: Arc<TransactionOrchestrator<NetworkAuthorityClient>>,
47    metrics: Arc<JsonRpcMetrics>,
48}
49
50impl TransactionExecutionApi {
51    pub fn new(
52        state: Arc<AuthorityState>,
53        transaction_orchestrator: Arc<TransactionOrchestrator<NetworkAuthorityClient>>,
54        metrics: Arc<JsonRpcMetrics>,
55    ) -> Self {
56        Self {
57            state,
58            transaction_orchestrator,
59            metrics,
60        }
61    }
62
63    pub fn convert_bytes<T: serde::de::DeserializeOwned>(
64        &self,
65        tx_bytes: Base64,
66    ) -> Result<T, SuiRpcInputError> {
67        let data: T = bcs::from_bytes(&tx_bytes.to_vec()?)?;
68        Ok(data)
69    }
70
71    #[allow(clippy::type_complexity)]
72    fn prepare_execute_transaction_block(
73        &self,
74        tx_bytes: Base64,
75        signatures: Vec<Base64>,
76        opts: Option<SuiTransactionBlockResponseOptions>,
77    ) -> Result<
78        (
79            ExecuteTransactionRequestV3,
80            SuiTransactionBlockResponseOptions,
81            SuiAddress,
82            Vec<InputObjectKind>,
83            Transaction,
84            Option<SuiTransactionBlock>,
85            Vec<u8>,
86        ),
87        SuiRpcInputError,
88    > {
89        let opts = opts.unwrap_or_default();
90
91        let tx_data: TransactionData = self.convert_bytes(tx_bytes)?;
92        let sender = tx_data.sender();
93        let input_objs = tx_data.input_objects().unwrap_or_default();
94
95        let mut sigs = Vec::new();
96        for sig in signatures {
97            sigs.push(GenericSignature::from_bytes(&sig.to_vec()?)?);
98        }
99        let txn = Transaction::from_generic_sig_data(tx_data, sigs);
100        let raw_transaction = if opts.show_raw_input {
101            bcs::to_bytes(txn.data())?
102        } else {
103            vec![]
104        };
105        let transaction = if opts.show_input {
106            let epoch_store = self.state.load_epoch_store_one_call_per_task();
107            Some(SuiTransactionBlock::try_from(
108                txn.data().clone(),
109                epoch_store.module_cache(),
110            )?)
111        } else {
112            None
113        };
114
115        let request = ExecuteTransactionRequestV3 {
116            transaction: txn.clone(),
117            include_events: opts.show_events,
118            include_input_objects: opts.show_balance_changes || opts.show_object_changes,
119            include_output_objects: opts.show_balance_changes
120                || opts.show_object_changes
121                // In order to resolve events, we may need access to the newly published packages.
122                || opts.show_events,
123            include_auxiliary_data: false,
124        };
125
126        Ok((
127            request,
128            opts,
129            sender,
130            input_objs,
131            txn,
132            transaction,
133            raw_transaction,
134        ))
135    }
136
137    async fn execute_transaction_block(
138        &self,
139        tx_bytes: Base64,
140        signatures: Vec<Base64>,
141        opts: Option<SuiTransactionBlockResponseOptions>,
142        request_type: Option<ExecuteTransactionRequestType>,
143    ) -> Result<SuiTransactionBlockResponse, Error> {
144        let request_type =
145            request_type.unwrap_or(ExecuteTransactionRequestType::WaitForEffectsCert);
146        let (request, opts, sender, input_objs, txn, transaction, raw_transaction) =
147            self.prepare_execute_transaction_block(tx_bytes, signatures, opts)?;
148        let digest = *txn.digest();
149
150        let transaction_orchestrator = self.transaction_orchestrator.clone();
151        let orch_timer = self.metrics.orchestrator_latency_ms.start_timer();
152        let (response, is_executed_locally) = transaction_orchestrator
153            .execute_transaction_block(request, request_type, None)
154            .await
155            .map_err(Error::from)?;
156        drop(orch_timer);
157
158        self.handle_post_orchestration(
159            response,
160            is_executed_locally,
161            opts,
162            digest,
163            input_objs,
164            transaction,
165            raw_transaction,
166            sender,
167        )
168        .await
169    }
170
171    async fn handle_post_orchestration(
172        &self,
173        response: ExecuteTransactionResponseV3,
174        is_executed_locally: bool,
175        opts: SuiTransactionBlockResponseOptions,
176        digest: TransactionDigest,
177        input_objs: Vec<InputObjectKind>,
178        transaction: Option<SuiTransactionBlock>,
179        raw_transaction: Vec<u8>,
180        sender: SuiAddress,
181    ) -> Result<SuiTransactionBlockResponse, Error> {
182        let _post_orch_timer = self.metrics.post_orchestrator_latency_ms.start_timer();
183
184        let events = if opts.show_events {
185            let epoch_store = self.state.load_epoch_store_one_call_per_task();
186            let backing_package_store = PostExecutionPackageResolver::new(
187                self.state.get_backing_package_store().clone(),
188                &response.output_objects,
189            );
190            let mut layout_resolver = epoch_store
191                .executor()
192                .type_layout_resolver(Box::new(backing_package_store));
193            Some(SuiTransactionBlockEvents::try_from(
194                response.events.unwrap_or_default(),
195                digest,
196                None,
197                layout_resolver.as_mut(),
198            )?)
199        } else {
200            None
201        };
202
203        let object_cache = match (response.input_objects, response.output_objects) {
204            (Some(input_objects), Some(output_objects)) => {
205                let mut object_cache = ObjectProviderCache::new(self.state.clone());
206                object_cache.insert_objects_into_cache(input_objects);
207                object_cache.insert_objects_into_cache(output_objects);
208                Some(object_cache)
209            }
210            _ => None,
211        };
212
213        let balance_changes = match &object_cache {
214            Some(object_cache) if opts.show_balance_changes => Some(
215                get_balance_changes_from_effect(
216                    object_cache,
217                    &response.effects.effects,
218                    input_objs,
219                    None,
220                )
221                .await?,
222            ),
223            _ => None,
224        };
225
226        let object_changes = match &object_cache {
227            Some(object_cache) if opts.show_object_changes => Some(
228                get_object_changes(
229                    object_cache,
230                    &response.effects.effects,
231                    sender,
232                    response.effects.effects.modified_at_versions(),
233                    response.effects.effects.all_changed_objects(),
234                    response.effects.effects.all_removed_objects(),
235                )
236                .await?,
237            ),
238            _ => None,
239        };
240
241        let raw_effects = if opts.show_raw_effects {
242            bcs::to_bytes(&response.effects.effects)?
243        } else {
244            vec![]
245        };
246
247        Ok(SuiTransactionBlockResponse {
248            digest,
249            transaction,
250            raw_transaction,
251            effects: opts
252                .show_effects
253                .then_some(response.effects.effects.try_into()?),
254            events,
255            object_changes,
256            balance_changes,
257            timestamp_ms: None,
258            confirmed_local_execution: Some(is_executed_locally),
259            checkpoint: None,
260            errors: vec![],
261            raw_effects,
262        })
263    }
264
265    pub fn prepare_dry_run_transaction_block(
266        &self,
267        tx_bytes: Base64,
268    ) -> Result<(TransactionData, TransactionDigest, Vec<InputObjectKind>), SuiRpcInputError> {
269        let tx_data: TransactionData = self.convert_bytes(tx_bytes)?;
270        let input_objs = tx_data.input_objects()?;
271        let intent_msg = IntentMessage::new(
272            Intent {
273                version: IntentVersion::V0,
274                scope: IntentScope::TransactionData,
275                app_id: AppId::Sui,
276            },
277            tx_data,
278        );
279        let txn_digest = TransactionDigest::new(default_hash(&intent_msg.value));
280        Ok((intent_msg.value, txn_digest, input_objs))
281    }
282
283    async fn dry_run_transaction_block(
284        &self,
285        tx_bytes: Base64,
286    ) -> Result<DryRunTransactionBlockResponse, Error> {
287        let (txn_data, txn_digest, input_objs) =
288            self.prepare_dry_run_transaction_block(tx_bytes)?;
289        let sender = txn_data.sender();
290        let (resp, written_objects, transaction_effects, mock_gas) = self
291            .state
292            .dry_exec_transaction(txn_data.clone(), txn_digest)
293            .await?;
294        let object_cache = ObjectProviderCache::new_with_cache(self.state.clone(), written_objects);
295        let balance_changes = get_balance_changes_from_effect(
296            &object_cache,
297            &transaction_effects,
298            input_objs,
299            mock_gas,
300        )
301        .await?;
302        let object_changes = get_object_changes(
303            &object_cache,
304            &transaction_effects,
305            sender,
306            transaction_effects.modified_at_versions(),
307            transaction_effects.all_changed_objects(),
308            transaction_effects.all_removed_objects(),
309        )
310        .await?;
311
312        Ok(DryRunTransactionBlockResponse {
313            effects: resp.effects,
314            events: resp.events,
315            object_changes,
316            balance_changes,
317            input: resp.input,
318            execution_error_source: resp.execution_error_source,
319            suggested_gas_price: resp.suggested_gas_price,
320        })
321    }
322}
323
324#[async_trait]
325impl WriteApiServer for TransactionExecutionApi {
326    #[instrument(skip_all)]
327    async fn execute_transaction_block(
328        &self,
329        tx_bytes: Base64,
330        signatures: Vec<Base64>,
331        opts: Option<SuiTransactionBlockResponseOptions>,
332        request_type: Option<ExecuteTransactionRequestType>,
333    ) -> RpcResult<SuiTransactionBlockResponse> {
334        with_tracing!(Duration::from_secs(10), async move {
335            self.execute_transaction_block(tx_bytes, signatures, opts, request_type)
336                .await
337        })
338    }
339
340    #[instrument(skip(self))]
341    async fn dev_inspect_transaction_block(
342        &self,
343        sender_address: SuiAddress,
344        tx_bytes: Base64,
345        gas_price: Option<BigInt<u64>>,
346        _epoch: Option<BigInt<u64>>,
347        additional_args: Option<DevInspectArgs>,
348    ) -> RpcResult<DevInspectResults> {
349        with_tracing!(async move {
350            let DevInspectArgs {
351                gas_sponsor,
352                gas_budget,
353                gas_objects,
354                show_raw_txn_data_and_effects,
355                skip_checks,
356            } = additional_args.unwrap_or_default();
357            let tx_kind: TransactionKind = self.convert_bytes(tx_bytes)?;
358            self.state
359                .dev_inspect_transaction_block(
360                    sender_address,
361                    tx_kind,
362                    gas_price.map(|i| *i),
363                    gas_budget.map(|i| *i),
364                    gas_sponsor,
365                    gas_objects,
366                    show_raw_txn_data_and_effects,
367                    skip_checks,
368                )
369                .await
370                .map_err(Error::from)
371        })
372    }
373
374    #[instrument(skip(self))]
375    async fn dry_run_transaction_block(
376        &self,
377        tx_bytes: Base64,
378    ) -> RpcResult<DryRunTransactionBlockResponse> {
379        with_tracing!(async move { self.dry_run_transaction_block(tx_bytes).await })
380    }
381}
382
383impl SuiRpcModule for TransactionExecutionApi {
384    fn rpc(self) -> RpcModule<Self> {
385        self.into_rpc()
386    }
387
388    fn rpc_doc_module() -> Module {
389        WriteApiOpenRpc::module_doc()
390    }
391}