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