1use 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 || 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}