1use std::{sync::Arc, time::Duration};
6
7use async_trait::async_trait;
8use fastcrypto::encoding::Base64;
9use iota_core::{
10 authority::AuthorityState, authority_client::NetworkAuthorityClient,
11 transaction_orchestrator::TransactionOrchestrator,
12};
13use iota_json::IotaJsonValue;
14use iota_json_rpc_api::{JsonRpcMetrics, WriteApiOpenRpc, WriteApiServer};
15use iota_json_rpc_types::{
16 DevInspectArgs, DevInspectResults, DryRunTransactionBlockResponse,
17 ExecuteTransactionRequestType as ExecuteTransactionRequestTypeSchema, IotaExecutionStatus,
18 IotaMoveViewCallResults, IotaTransactionBlock, IotaTransactionBlockData,
19 IotaTransactionBlockEffects, IotaTransactionBlockEffectsAPI, IotaTransactionBlockEvents,
20 IotaTransactionBlockResponse, IotaTransactionBlockResponseOptions, IotaTypeTag,
21 MoveFunctionName,
22};
23use iota_metrics::spawn_monitored_task;
24use iota_open_rpc::Module;
25use iota_package_resolver::{
26 Package, PackageStore, Resolver, error::Error as PackageResolverError,
27};
28use iota_sdk_types::{
29 Address, GasPayment, ObjectId, Transaction, TransactionDigest, TransactionExpiration,
30 TransactionKind, TransactionV1, UserSignature,
31};
32use iota_transaction_builder::TransactionBuilder;
33use iota_types::{
34 effects::{TransactionEffectsAPI, TransactionEffectsExt},
35 error::IotaError,
36 execution_config_utils::to_binary_config,
37 inner_temporary_store::{
38 ObjectMapPackageStore, PackageStoreWithFallback, TemporaryModuleResolver,
39 },
40 iota_serde::BigInt,
41 quorum_driver_types::{
42 ExecuteTransactionRequestType, ExecuteTransactionRequestV1, ExecuteTransactionResponseV1,
43 },
44 storage::PostExecutionPackageResolver,
45 transaction::{InputObjectKind, TransactionAPI, TransactionEnvelope},
46 transaction_executor::{SimulateTransactionResult, VmChecks},
47};
48use jsonrpsee::{RpcModule, core::RpcResult};
49use tracing::{Instrument, instrument};
50
51use crate::{
52 IotaRpcModule, ObjectProviderCache,
53 authority_state::StateRead,
54 error::{Error, IotaRpcInputError},
55 get_balance_changes_from_effect, get_object_changes,
56 logger::FutureWithTracing,
57 transaction_builder_api::AuthorityStateDataReader,
58};
59
60#[derive(Clone)]
61pub struct TransactionExecutionApi {
62 state: Arc<dyn StateRead>,
63 transaction_orchestrator: Arc<TransactionOrchestrator<NetworkAuthorityClient>>,
64 metrics: Arc<JsonRpcMetrics>,
65 transaction_builder: TransactionBuilder,
66}
67
68impl TransactionExecutionApi {
69 pub fn new(
70 state: Arc<AuthorityState>,
71 transaction_orchestrator: Arc<TransactionOrchestrator<NetworkAuthorityClient>>,
72 metrics: Arc<JsonRpcMetrics>,
73 ) -> Self {
74 let reader = Arc::new(AuthorityStateDataReader::new(state.clone()));
75 Self {
76 state,
77 transaction_orchestrator,
78 metrics,
79 transaction_builder: TransactionBuilder::new(reader),
80 }
81 }
82
83 pub fn convert_bytes<T: serde::de::DeserializeOwned>(
84 &self,
85 tx_bytes: Base64,
86 ) -> Result<T, IotaRpcInputError> {
87 let data: T = bcs::from_bytes(&tx_bytes.to_vec()?)?;
88 Ok(data)
89 }
90
91 #[expect(clippy::type_complexity)]
92 fn prepare_execute_transaction_block(
93 &self,
94 tx_bytes: Base64,
95 signatures: Vec<Base64>,
96 opts: Option<IotaTransactionBlockResponseOptions>,
97 ) -> Result<
98 (
99 ExecuteTransactionRequestV1,
100 IotaTransactionBlockResponseOptions,
101 Address,
102 Vec<InputObjectKind>,
103 TransactionEnvelope,
104 Option<IotaTransactionBlock>,
105 Vec<u8>,
106 ),
107 IotaRpcInputError,
108 > {
109 let opts = opts.unwrap_or_default();
110 let tx: Transaction = self.convert_bytes(tx_bytes)?;
111 let sender = tx.sender();
112 let input_objs = tx.input_objects().unwrap_or_default();
113
114 let mut sigs = Vec::new();
115 for sig in signatures {
116 sigs.push(
117 UserSignature::from_base64(&sig.encoded())
118 .map_err(|e| IotaRpcInputError::GenericInvalid(e.to_string()))?,
119 );
120 }
121 let txn = TransactionEnvelope::from_user_sig_data(tx, sigs);
122 let raw_transaction = if opts.show_raw_input {
123 bcs::to_bytes(txn.data())?
124 } else {
125 vec![]
126 };
127 let transaction = if opts.show_input {
128 let epoch_store = self.state.load_epoch_store_one_call_per_task();
129
130 Some(IotaTransactionBlock::try_from(
131 txn.data().clone(),
132 epoch_store.module_cache(),
133 *txn.digest(),
134 )?)
135 } else {
136 None
137 };
138
139 let request = ExecuteTransactionRequestV1 {
140 transaction: txn.clone(),
141 include_events: opts.show_events,
142 include_input_objects: opts.show_balance_changes || opts.show_object_changes,
143 include_output_objects: opts.show_balance_changes
144 || opts.show_object_changes
145 || opts.show_events,
147 include_auxiliary_data: false,
148 };
149
150 Ok((
151 request,
152 opts,
153 sender,
154 input_objs,
155 txn,
156 transaction,
157 raw_transaction,
158 ))
159 }
160
161 #[instrument("json_rpc_api_execute_transaction_block", level = "trace", skip_all)]
162 async fn execute_transaction_block(
163 &self,
164 tx_bytes: Base64,
165 signatures: Vec<Base64>,
166 opts: Option<IotaTransactionBlockResponseOptions>,
167 request_type: Option<ExecuteTransactionRequestType>,
168 ) -> Result<IotaTransactionBlockResponse, Error> {
169 let request_type =
170 request_type.unwrap_or(ExecuteTransactionRequestType::WaitForEffectsCert);
171 let (request, opts, sender, input_objs, txn, transaction, raw_transaction) =
172 self.prepare_execute_transaction_block(tx_bytes, signatures, opts)?;
173 let digest = *txn.digest();
174
175 let transaction_orchestrator = self.transaction_orchestrator.clone();
176 let orch_timer = self.metrics.orchestrator_latency_ms.start_timer();
177
178 tracing::trace!(
179 "Spawning transaction orchestrator task for transaction: {}",
180 digest
181 );
182 let (response, is_executed_locally) = spawn_monitored_task!(
183 transaction_orchestrator.execute_transaction_block(request, request_type, None)
184 )
185 .await?
186 .map_err(Error::from)?;
187 drop(orch_timer);
188
189 self.handle_post_orchestration(
190 response,
191 is_executed_locally,
192 opts,
193 digest,
194 input_objs,
195 transaction,
196 raw_transaction,
197 sender,
198 )
199 .await
200 }
201
202 #[instrument(level = "trace", skip_all)]
203 async fn handle_post_orchestration(
204 &self,
205 response: ExecuteTransactionResponseV1,
206 is_executed_locally: bool,
207 opts: IotaTransactionBlockResponseOptions,
208 digest: TransactionDigest,
209 input_objs: Vec<InputObjectKind>,
210 transaction: Option<IotaTransactionBlock>,
211 raw_transaction: Vec<u8>,
212 sender: Address,
213 ) -> Result<IotaTransactionBlockResponse, Error> {
214 let _post_orch_timer = self.metrics.post_orchestrator_latency_ms.start_timer();
215
216 let events = if opts.show_events {
217 tracing::trace!("Resolving events");
218 let epoch_store = self.state.load_epoch_store_one_call_per_task();
219 let backing_package_store = PostExecutionPackageResolver::new(
220 self.state.get_backing_package_store().clone(),
221 &response.output_objects,
222 );
223 let mut layout_resolver = epoch_store
224 .executor()
225 .type_layout_resolver(Box::new(backing_package_store));
226 Some(IotaTransactionBlockEvents::try_from(
227 response.events.unwrap_or_default(),
228 digest,
229 None,
230 layout_resolver.as_mut(),
231 )?)
232 } else {
233 None
234 };
235
236 let object_cache = if (opts.show_balance_changes || opts.show_object_changes)
241 && (response.input_objects.is_some() || response.output_objects.is_some())
242 {
243 let mut object_cache = ObjectProviderCache::new(self.state.clone());
244 if let Some(input_objects) = response.input_objects {
245 object_cache.insert_objects_into_cache(input_objects);
246 }
247 if let Some(output_objects) = response.output_objects {
248 object_cache.insert_objects_into_cache(output_objects);
249 }
250 Some(object_cache)
251 } else {
252 None
253 };
254
255 let balance_changes = match &object_cache {
256 Some(object_cache) if opts.show_balance_changes => Some(
257 get_balance_changes_from_effect(
258 object_cache,
259 &response.effects.effects,
260 input_objs,
261 None,
262 )
263 .instrument(tracing::trace_span!("resolving balance changes"))
264 .await?,
265 ),
266 _ => None,
267 };
268
269 let object_changes = match &object_cache {
270 Some(object_cache) if opts.show_object_changes => Some(
271 get_object_changes(
272 object_cache,
273 sender,
274 response.effects.effects.modified_at_versions(),
275 response.effects.effects.all_changed_objects(),
276 response.effects.effects.all_removed_objects(),
277 )
278 .instrument(tracing::trace_span!("resolving object changes"))
279 .await?,
280 ),
281 _ => None,
282 };
283
284 let raw_effects = if opts.show_raw_effects {
285 bcs::to_bytes(&response.effects.effects)?
286 } else {
287 vec![]
288 };
289 let resolver = Resolver::new(self.clone());
290
291 let effects = if opts.show_effects {
292 Some(
293 IotaTransactionBlockEffects::from_native_with_clever_error(
294 response.effects.effects,
295 &resolver,
296 )
297 .await,
298 )
299 } else {
300 None
301 };
302
303 let errors = match effects.as_ref().map(|e| e.status()) {
304 Some(IotaExecutionStatus::Failure { error }) => vec![error.clone()],
305 _ => vec![],
306 };
307
308 Ok(IotaTransactionBlockResponse {
309 digest,
310 transaction,
311 raw_transaction,
312 effects,
313 events,
314 object_changes,
315 balance_changes,
316 timestamp_ms: None,
317 confirmed_local_execution: Some(is_executed_locally),
318 checkpoint: None,
319 errors,
320 raw_effects,
321 })
322 }
323
324 pub fn prepare_dry_run_transaction_block(
325 &self,
326 tx_bytes: Base64,
327 ) -> Result<(Transaction, Vec<InputObjectKind>), IotaRpcInputError> {
328 let tx: Transaction = self.convert_bytes(tx_bytes)?;
329 let input_objs = tx.input_objects()?;
330 Ok((tx, input_objs))
331 }
332
333 fn report_simulation_gas(
337 transaction: &mut Transaction,
338 simulation: &SimulateTransactionResult,
339 ) {
340 iota_types::gas::report_simulation_gas(
341 transaction.gas_data_mut(),
342 &simulation.gas_data,
343 simulation.effects.gas_cost_summary().gas_used(),
344 );
345 }
346
347 fn dry_run_transaction_block_impl(
353 &self,
354 mut tx: Transaction,
355 ) -> Result<
356 (
357 SimulateTransactionResult,
358 IotaTransactionBlockData,
359 IotaTransactionBlockEvents,
360 ),
361 Error,
362 > {
363 let epoch_store = Arc::clone(&self.state.load_epoch_store_one_call_per_task());
368
369 let mut simulation = self.state.simulate_transaction_in_epoch(
370 &epoch_store,
371 tx.clone(),
372 VmChecks::Enabled,
373 )?;
374
375 Self::report_simulation_gas(&mut tx, &simulation);
376
377 let tx_digest = *simulation.effects.transaction_digest();
378 let (input, events) = {
381 let mut layout_resolver = epoch_store.executor().type_layout_resolver(Box::new(
382 PackageStoreWithFallback::new(
383 ObjectMapPackageStore(&simulation.output_objects),
384 self.state.get_backing_package_store(),
385 ),
386 ));
387 let module_cache = TemporaryModuleResolver::new(
388 &simulation.output_objects,
389 to_binary_config(epoch_store.protocol_config()),
390 epoch_store.module_cache().clone(),
391 );
392
393 let input =
394 IotaTransactionBlockData::try_from_with_module_cache(tx, &module_cache, tx_digest)
395 .map_err(|e| IotaError::TransactionSerialization {
396 error: format!(
397 "Failed to convert transaction to IotaTransactionBlockData: {e}"
398 ),
399 })?;
400 let events = IotaTransactionBlockEvents::try_from(
401 simulation.events.take().unwrap_or_default(),
402 tx_digest,
403 None,
404 layout_resolver.as_mut(),
405 )?;
406
407 (input, events)
408 };
409
410 Ok((simulation, input, events))
411 }
412
413 async fn dry_run_transaction_block(
414 &self,
415 tx_bytes: Base64,
416 ) -> Result<DryRunTransactionBlockResponse, Error> {
417 let (txn_data, input_objs) = self.prepare_dry_run_transaction_block(tx_bytes)?;
418 let sender = txn_data.sender();
419
420 let (simulation, input, events) = {
423 let this = self.clone();
424 tokio::task::spawn_blocking(move || this.dry_run_transaction_block_impl(txn_data))
425 .await
426 .map_err(Error::from)??
427 };
428
429 let execution_error_source = simulation
430 .execution_result
431 .as_ref()
432 .err()
433 .and_then(|e| e.source().as_ref().map(|e| e.to_string()));
434
435 let object_cache =
436 ObjectProviderCache::new_with_cache(self.state.clone(), &simulation.output_objects);
437 let balance_changes = get_balance_changes_from_effect(
438 &object_cache,
439 &simulation.effects,
440 input_objs,
441 simulation.mock_gas_id,
442 )
443 .await?;
444 let object_changes = get_object_changes(
445 &object_cache,
446 sender,
447 simulation.effects.modified_at_versions(),
448 simulation.effects.all_changed_objects(),
449 simulation.effects.all_removed_objects(),
450 )
451 .await?;
452
453 let resolver = Resolver::new(self.clone());
454 let effects = IotaTransactionBlockEffects::from_native_with_clever_error(
455 simulation.effects,
456 &resolver,
457 )
458 .await;
459
460 Ok(DryRunTransactionBlockResponse {
461 effects,
462 events,
463 object_changes,
464 balance_changes,
465 input,
466 suggested_gas_price: simulation.suggested_gas_price,
467 execution_error_source,
468 })
469 }
470
471 fn dev_inspect_transaction_impl(
472 &self,
473 sender: Address,
474 transaction_kind: TransactionKind,
475 gas_price: Option<u64>,
476 args: DevInspectArgs,
477 ) -> Result<DevInspectResults, Error> {
478 let DevInspectArgs {
479 gas_sponsor,
480 gas_budget,
481 gas_objects,
482 show_raw_txn_data_and_effects,
483 skip_checks,
484 } = args;
485 let show_raw_txn_data_and_effects = show_raw_txn_data_and_effects.unwrap_or(false);
486 let skip_checks = skip_checks.unwrap_or(true);
487
488 let epoch_store = Arc::clone(&self.state.load_epoch_store_one_call_per_task());
493
494 let transaction = Transaction::V1(TransactionV1 {
495 kind: transaction_kind,
496 sender,
497 gas_payment: GasPayment {
498 objects: gas_objects.unwrap_or_default(),
504 owner: gas_sponsor.unwrap_or(sender),
505 price: gas_price.unwrap_or_default(),
506 budget: gas_budget.unwrap_or_default(),
507 },
508 expiration: TransactionExpiration::None,
509 });
510
511 let checks = if skip_checks {
512 VmChecks::Disabled
513 } else {
514 VmChecks::Enabled
515 };
516 let mut reported_transaction = show_raw_txn_data_and_effects.then(|| transaction.clone());
519 let simulation =
520 self.state
521 .simulate_transaction_in_epoch(&epoch_store, transaction, checks)?;
522
523 let raw_txn_data = match reported_transaction.as_mut() {
524 Some(transaction) => {
525 Self::report_simulation_gas(transaction, &simulation);
526 bcs::to_bytes(transaction).map_err(|_| IotaError::TransactionSerialization {
527 error: "Failed to serialize transaction during dev inspect".to_string(),
528 })?
529 }
530 None => vec![],
531 };
532
533 let raw_effects = if show_raw_txn_data_and_effects {
534 bcs::to_bytes(&simulation.effects).map_err(|_| IotaError::TransactionSerialization {
535 error: "Failed to serialize transaction effects during dev inspect".to_string(),
536 })?
537 } else {
538 vec![]
539 };
540
541 let mut layout_resolver =
544 epoch_store
545 .executor()
546 .type_layout_resolver(Box::new(PackageStoreWithFallback::new(
547 ObjectMapPackageStore(&simulation.output_objects),
548 self.state.get_backing_package_store(),
549 )));
550
551 Ok(DevInspectResults::new(
552 simulation.effects,
553 simulation.events.unwrap_or_default(),
554 simulation.execution_result,
555 raw_txn_data,
556 raw_effects,
557 layout_resolver.as_mut(),
558 )?)
559 }
560
561 async fn dev_inspect_transaction(
562 &self,
563 sender: Address,
564 transaction_kind: TransactionKind,
565 gas_price: Option<u64>,
566 args: DevInspectArgs,
567 ) -> Result<DevInspectResults, Error> {
568 let this = self.clone();
571 tokio::task::spawn_blocking(move || {
572 this.dev_inspect_transaction_impl(sender, transaction_kind, gas_price, args)
573 })
574 .await
575 .map_err(Error::from)?
576 }
577}
578
579#[async_trait]
580impl WriteApiServer for TransactionExecutionApi {
581 #[instrument(skip(self, tx_bytes, signatures))]
582 async fn execute_transaction_block(
583 &self,
584 tx_bytes: Base64,
585 signatures: Vec<Base64>,
586 opts: Option<IotaTransactionBlockResponseOptions>,
587 request_type: Option<ExecuteTransactionRequestTypeSchema>,
588 ) -> RpcResult<IotaTransactionBlockResponse> {
589 self.execute_transaction_block(tx_bytes, signatures, opts, request_type.map(Into::into))
590 .trace_timeout(Duration::from_secs(10))
591 .await
592 }
593
594 #[instrument(skip(self, arguments))]
596 async fn view_function_call(
597 &self,
598 function_name: String,
599 type_args: Option<Vec<IotaTypeTag>>,
600 arguments: Vec<IotaJsonValue>,
601 ) -> RpcResult<IotaMoveViewCallResults> {
602 let MoveFunctionName {
603 package,
604 module,
605 function,
606 } = function_name.as_str().parse().map_err(Error::from)?;
607 let sender = Address::ZERO;
608 let tx_kind = self
609 .transaction_builder
610 .move_view_call_tx_kind(
611 package,
612 &module,
613 &function,
614 type_args.unwrap_or_default(),
615 arguments,
616 )
617 .await
618 .map_err(Error::from)?;
619 let dev_inspect_results = self
620 .dev_inspect_transaction(sender, tx_kind, None, DevInspectArgs::default())
621 .await?;
622 Ok(
623 IotaMoveViewCallResults::from_dev_inspect_results(self.clone(), dev_inspect_results)
624 .await
625 .map_err(Error::from)?,
626 )
627 }
628
629 #[instrument(
630 skip(self, sender_address, tx_bytes, additional_args),
631 fields(sender_address = %sender_address)
632 )]
633 async fn dev_inspect_transaction_block(
634 &self,
635 sender_address: Address,
636 tx_bytes: Base64,
637 gas_price: Option<BigInt<u64>>,
638 _epoch: Option<BigInt<u64>>,
639 additional_args: Option<DevInspectArgs>,
640 ) -> RpcResult<DevInspectResults> {
641 async move {
642 let tx_kind: TransactionKind = self.convert_bytes(tx_bytes)?;
643 self.dev_inspect_transaction(
644 sender_address,
645 tx_kind,
646 gas_price.map(|i| *i),
647 additional_args.unwrap_or_default(),
648 )
649 .await
650 }
651 .trace()
652 .await
653 }
654
655 #[instrument(skip(self, tx_bytes))]
656 async fn dry_run_transaction_block(
657 &self,
658 tx_bytes: Base64,
659 ) -> RpcResult<DryRunTransactionBlockResponse> {
660 self.dry_run_transaction_block(tx_bytes).trace().await
661 }
662}
663
664impl IotaRpcModule for TransactionExecutionApi {
665 fn rpc(self) -> RpcModule<Self> {
666 self.into_rpc()
667 }
668
669 fn rpc_doc_module() -> Module {
670 WriteApiOpenRpc::module_doc()
671 }
672}
673
674#[async_trait]
675impl PackageStore for TransactionExecutionApi {
676 async fn fetch(&self, id: Address) -> Result<Arc<Package>, PackageResolverError> {
677 let backing_store = self.state.get_backing_package_store();
678 match backing_store.get_package_object(&ObjectId::new(id.into_bytes())) {
679 Ok(Some(pkg)) => Ok(Arc::new(Package::read_from_package(pkg.move_package())?)),
680 Ok(None) => Err(PackageResolverError::PackageNotFound(id)),
681 Err(e) => Err(PackageResolverError::Store {
682 store: "Node",
683 source: Arc::new(e),
684 }),
685 }
686 }
687}