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::signature::GenericSignature;
34use sui_types::storage::PostExecutionPackageResolver;
35use sui_types::sui_serde::BigInt;
36use sui_types::transaction::{
37 InputObjectKind, Transaction, TransactionData, TransactionDataAPI, TransactionKind,
38};
39use sui_types::transaction_driver_types::{
40 ExecuteTransactionRequestType, ExecuteTransactionRequestV3, ExecuteTransactionResponseV3,
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 = if opts.show_balance_changes || opts.show_object_changes {
204 let mut object_cache = ObjectProviderCache::new(self.state.clone());
205 if let Some(input_objects) = response.input_objects {
206 object_cache.insert_objects_into_cache(input_objects);
207 }
208 if let Some(output_objects) = response.output_objects {
209 object_cache.insert_objects_into_cache(output_objects);
210 }
211 Some(object_cache)
212 } else {
213 None
214 };
215
216 let balance_changes = match &object_cache {
217 Some(object_cache) if opts.show_balance_changes => Some(
218 get_balance_changes_from_effect(
219 object_cache,
220 &response.effects.effects,
221 input_objs,
222 None,
223 )
224 .await?,
225 ),
226 _ => None,
227 };
228
229 let object_changes = match &object_cache {
230 Some(object_cache) if opts.show_object_changes => Some(
231 get_object_changes(
232 object_cache,
233 &response.effects.effects,
234 sender,
235 response.effects.effects.modified_at_versions(),
236 response.effects.effects.all_changed_objects(),
237 response.effects.effects.all_removed_objects(),
238 )
239 .await?,
240 ),
241 _ => None,
242 };
243
244 let raw_effects = if opts.show_raw_effects {
245 bcs::to_bytes(&response.effects.effects)?
246 } else {
247 vec![]
248 };
249
250 Ok(SuiTransactionBlockResponse {
251 digest,
252 transaction,
253 raw_transaction,
254 effects: opts
255 .show_effects
256 .then_some(response.effects.effects.try_into()?),
257 events,
258 object_changes,
259 balance_changes,
260 timestamp_ms: None,
261 confirmed_local_execution: Some(is_executed_locally),
262 checkpoint: None,
263 errors: vec![],
264 raw_effects,
265 })
266 }
267
268 pub fn prepare_dry_run_transaction_block(
269 &self,
270 tx_bytes: Base64,
271 ) -> Result<(TransactionData, TransactionDigest, Vec<InputObjectKind>), SuiRpcInputError> {
272 let tx_data: TransactionData = self.convert_bytes(tx_bytes)?;
273 let input_objs = tx_data.input_objects()?;
274 let intent_msg = IntentMessage::new(
275 Intent {
276 version: IntentVersion::V0,
277 scope: IntentScope::TransactionData,
278 app_id: AppId::Sui,
279 },
280 tx_data,
281 );
282 let txn_digest = TransactionDigest::new(default_hash(&intent_msg.value));
283 Ok((intent_msg.value, txn_digest, input_objs))
284 }
285
286 async fn dry_run_transaction_block(
287 &self,
288 tx_bytes: Base64,
289 ) -> Result<DryRunTransactionBlockResponse, Error> {
290 let (txn_data, txn_digest, input_objs) =
291 self.prepare_dry_run_transaction_block(tx_bytes)?;
292 let sender = txn_data.sender();
293 let (resp, written_objects, transaction_effects, mock_gas) = self
294 .state
295 .dry_exec_transaction(txn_data.clone(), txn_digest)
296 .await?;
297 let object_cache = ObjectProviderCache::new_with_cache(self.state.clone(), written_objects);
298 let balance_changes = get_balance_changes_from_effect(
299 &object_cache,
300 &transaction_effects,
301 input_objs,
302 mock_gas,
303 )
304 .await?;
305 let object_changes = get_object_changes(
306 &object_cache,
307 &transaction_effects,
308 sender,
309 transaction_effects.modified_at_versions(),
310 transaction_effects.all_changed_objects(),
311 transaction_effects.all_removed_objects(),
312 )
313 .await?;
314
315 Ok(DryRunTransactionBlockResponse {
316 effects: resp.effects,
317 events: resp.events,
318 object_changes,
319 balance_changes,
320 input: resp.input,
321 execution_error_source: resp.execution_error_source,
322 suggested_gas_price: resp.suggested_gas_price,
323 })
324 }
325}
326
327#[async_trait]
328impl WriteApiServer for TransactionExecutionApi {
329 #[instrument(skip_all)]
330 async fn execute_transaction_block(
331 &self,
332 tx_bytes: Base64,
333 signatures: Vec<Base64>,
334 opts: Option<SuiTransactionBlockResponseOptions>,
335 request_type: Option<ExecuteTransactionRequestType>,
336 ) -> RpcResult<SuiTransactionBlockResponse> {
337 with_tracing!(Duration::from_secs(10), async move {
338 self.execute_transaction_block(tx_bytes, signatures, opts, request_type)
339 .await
340 })
341 }
342
343 #[instrument(skip(self))]
344 async fn dev_inspect_transaction_block(
345 &self,
346 sender_address: SuiAddress,
347 tx_bytes: Base64,
348 gas_price: Option<BigInt<u64>>,
349 _epoch: Option<BigInt<u64>>,
350 additional_args: Option<DevInspectArgs>,
351 ) -> RpcResult<DevInspectResults> {
352 with_tracing!(async move {
353 let DevInspectArgs {
354 gas_sponsor,
355 gas_budget,
356 gas_objects,
357 show_raw_txn_data_and_effects,
358 skip_checks,
359 } = additional_args.unwrap_or_default();
360 let tx_kind: TransactionKind = self.convert_bytes(tx_bytes)?;
361 self.state
362 .dev_inspect_transaction_block(
363 sender_address,
364 tx_kind,
365 gas_price.map(|i| *i),
366 gas_budget.map(|i| *i),
367 gas_sponsor,
368 gas_objects,
369 show_raw_txn_data_and_effects,
370 skip_checks,
371 )
372 .await
373 .map_err(Error::from)
374 })
375 }
376
377 #[instrument(skip(self))]
378 async fn dry_run_transaction_block(
379 &self,
380 tx_bytes: Base64,
381 ) -> RpcResult<DryRunTransactionBlockResponse> {
382 with_tracing!(async move { self.dry_run_transaction_block(tx_bytes).await })
383 }
384}
385
386impl SuiRpcModule for TransactionExecutionApi {
387 fn rpc(self) -> RpcModule<Self> {
388 self.into_rpc()
389 }
390
391 fn rpc_doc_module() -> Module {
392 WriteApiOpenRpc::module_doc()
393 }
394}