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