1use std::{
10 collections::BTreeMap, net::SocketAddr, ops::Deref, path::Path, sync::Arc, time::Duration,
11};
12
13use futures::{
14 FutureExt,
15 future::{Either, Future, select},
16};
17use iota_common::{debug_fatal, sync::notify_read::NotifyRead};
18use iota_config::NodeConfig;
19use iota_metrics::{
20 TX_TYPE_SHARED_OBJ_TX, TX_TYPE_SINGLE_WRITER_TX, add_server_timing,
21 spawn_logged_monitored_task, spawn_monitored_task,
22};
23use iota_storage::write_path_pending_tx_log::WritePathPendingTransactionLog;
24use iota_types::{
25 base_types::TransactionDigest,
26 effects::TransactionEffectsAPI,
27 error::{IotaError, IotaResult},
28 iota_system_state::IotaSystemState,
29 messages_checkpoint::CheckpointSequenceNumber,
30 quorum_driver_types::{
31 EffectsFinalityInfo, ExecuteTransactionRequestType, ExecuteTransactionRequestV1,
32 ExecuteTransactionResponseV1, FinalizedEffects, IsTransactionExecutedLocally,
33 QuorumDriverEffectsQueueResult, QuorumDriverError, QuorumDriverResponse,
34 QuorumDriverResult,
35 },
36 transaction::{TransactionData, VerifiedTransaction},
37 transaction_driver_types::{
38 EffectsFinalityInfo as TdEffectsFinalityInfo, FinalizedEffects as TdFinalizedEffects,
39 },
40 transaction_executor::{SimulateTransactionResult, VmChecks},
41};
42use prometheus_filtered::{
43 Histogram, Registry,
44 core::{AtomicI64, AtomicU64, GenericCounter, GenericGauge},
45 register_histogram_vec_with_registry, register_int_counter_vec_with_registry,
46 register_int_counter_with_registry, register_int_gauge_vec_with_registry,
47 register_int_gauge_with_registry,
48};
49use tokio::{
50 sync::broadcast::{Receiver, error::RecvError},
51 task::JoinHandle,
52 time::timeout,
53};
54use tracing::{Instrument, debug, error, info, instrument, trace_span, warn};
55
56use crate::{
57 authority::{AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore},
58 authority_aggregator::AuthorityAggregator,
59 authority_client::{AuthorityAPI, NetworkAuthorityClient},
60 quorum_driver::{
61 QuorumDriverHandler, QuorumDriverHandlerBuilder, QuorumDriverMetrics,
62 reconfig_observer::{OnsiteReconfigObserver, ReconfigObserver},
63 },
64 transaction_driver::{
65 AggregatedRequestErrors, QuorumTransactionResponse, SubmitTransactionOptions,
66 TransactionDriver, TransactionDriverError, TransactionDriverMetrics,
67 reconfig_observer::OnsiteReconfigObserver as TdOnsiteReconfigObserver,
68 },
69 validator_client_monitor::ValidatorClientMetrics,
70};
71
72const LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(10);
75
76const WAIT_FOR_FINALITY_TIMEOUT: Duration = Duration::from_secs(30);
77
78enum Driver<A: Clone> {
82 Quorum(Arc<QuorumDriverHandler<A>>),
84 Transaction(Arc<TransactionDriver<A>>),
86}
87
88pub struct TransactionOrchestrator<A: Clone> {
93 driver: Driver<A>,
94 validator_state: Arc<AuthorityState>,
95 _local_executor_handle: Option<JoinHandle<()>>,
96 pending_tx_log: Arc<WritePathPendingTransactionLog>,
97 notifier: Arc<NotifyRead<TransactionDigest, QuorumDriverResult>>,
98 metrics: Arc<TransactionOrchestratorMetrics>,
99}
100
101impl TransactionOrchestrator<NetworkAuthorityClient> {
102 pub fn new_with_auth_aggregator(
103 validators: Arc<AuthorityAggregator<NetworkAuthorityClient>>,
104 validator_state: Arc<AuthorityState>,
105 reconfig_channel: Receiver<IotaSystemState>,
106 parent_path: &Path,
107 prometheus_registry: &Registry,
108 node_config: Option<&NodeConfig>,
109 ) -> Self {
110 let epoch_store = validator_state.load_epoch_store_one_call_per_task();
112 let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
113
114 let td_reconfig_observer = if use_transaction_driver {
116 Some(TdOnsiteReconfigObserver::new(
117 reconfig_channel.resubscribe(),
118 validator_state.get_object_cache_reader().clone(),
119 validator_state.clone_committee_store(),
120 validators.safe_client_metrics_base.clone(),
121 ))
122 } else {
123 None
124 };
125
126 let qd_reconfig_observer = if !use_transaction_driver {
128 Some(OnsiteReconfigObserver::new(
129 reconfig_channel.resubscribe(),
130 validator_state.get_object_cache_reader().clone(),
131 validator_state.clone_committee_store(),
132 validators.safe_client_metrics_base.clone(),
133 validators.metrics.deref().clone(),
134 ))
135 } else {
136 None
137 };
138
139 TransactionOrchestrator::new(
140 validators,
141 validator_state,
142 parent_path,
143 prometheus_registry,
144 qd_reconfig_observer,
145 td_reconfig_observer,
146 node_config,
147 )
148 }
149}
150
151impl<A> TransactionOrchestrator<A>
152where
153 A: AuthorityAPI + Send + Sync + 'static + Clone,
154 OnsiteReconfigObserver: ReconfigObserver<A>,
155 TdOnsiteReconfigObserver: crate::transaction_driver::reconfig_observer::ReconfigObserver<A>,
156{
157 pub fn new(
158 validators: Arc<AuthorityAggregator<A>>,
159 validator_state: Arc<AuthorityState>,
160 parent_path: &Path,
161 prometheus_registry: &Registry,
162 reconfig_observer: Option<OnsiteReconfigObserver>,
163 td_reconfig_observer: Option<TdOnsiteReconfigObserver>,
164 node_config: Option<&NodeConfig>,
165 ) -> Self {
166 let epoch_store = validator_state.load_epoch_store_one_call_per_task();
168 let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
169
170 let notifier = Arc::new(NotifyRead::new());
171 let metrics = Arc::new(TransactionOrchestratorMetrics::new(prometheus_registry));
172 let pending_tx_log = Arc::new(WritePathPendingTransactionLog::new(
173 parent_path.join("fullnode_pending_transactions"),
174 ));
175
176 let (driver, _local_executor_handle) = if !use_transaction_driver {
177 let qd_metrics = Arc::new(QuorumDriverMetrics::new(prometheus_registry));
178 let reconfig_observer = Arc::new(
179 reconfig_observer
180 .expect("QuorumDriver reconfig observer required when P-COOL is disabled"),
181 );
182 let handler = Arc::new(
183 QuorumDriverHandlerBuilder::new(validators, qd_metrics)
184 .with_notifier(notifier.clone())
185 .with_reconfig_observer(reconfig_observer)
186 .start(),
187 );
188 let effects_receiver = handler.subscribe_to_effects();
189 let pending_tx_log_clone = pending_tx_log.clone();
190 let local_executor_handle = spawn_monitored_task!(async move {
191 Self::loop_pending_transaction_log(effects_receiver, pending_tx_log_clone).await;
192 });
193 Self::schedule_txes_in_log(pending_tx_log.clone(), handler.clone());
197 (Driver::Quorum(handler), Some(local_executor_handle))
198 } else {
199 let td_metrics = Arc::new(TransactionDriverMetrics::new(prometheus_registry));
200 let client_metrics = Arc::new(ValidatorClientMetrics::new(prometheus_registry));
201 let observer = td_reconfig_observer
202 .expect("TransactionDriver reconfig observer required when P-COOL is enabled");
203 (
204 Driver::Transaction(TransactionDriver::new(
205 validators,
206 Arc::new(observer),
207 td_metrics,
208 node_config,
209 client_metrics,
210 )),
211 None,
212 )
213 };
214
215 Self {
216 driver,
217 validator_state,
218 _local_executor_handle,
219 pending_tx_log,
220 notifier,
221 metrics,
222 }
223 }
224}
225
226impl<A> TransactionOrchestrator<A>
227where
228 A: AuthorityAPI + Send + Sync + 'static + Clone,
229{
230 #[instrument(name = "tx_orchestrator_execute_transaction_block", level = "trace", skip_all,
231 fields(
232 tx_digest = ?request.transaction.digest(),
233 tx_type = ?request_type,
234 ),
235 err)]
236 pub async fn execute_transaction_block(
237 &self,
238 request: ExecuteTransactionRequestV1,
239 request_type: ExecuteTransactionRequestType,
240 client_addr: Option<SocketAddr>,
241 ) -> Result<(ExecuteTransactionResponseV1, IsTransactionExecutedLocally), QuorumDriverError>
242 {
243 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
244
245 let include_events = request.include_events;
250 let include_input_objects = request.include_input_objects;
251 let include_output_objects = request.include_output_objects;
252
253 let transaction = epoch_store
254 .verify_transaction(request.transaction.clone())
255 .map_err(QuorumDriverError::InvalidUserSignature)?;
256
257 let wait_for_local_execution = matches!(
258 request_type,
259 ExecuteTransactionRequestType::WaitForLocalExecution
260 );
261 let tx_digest = *transaction.digest();
262
263 let (mut response, seq) = match (&self.driver, wait_for_local_execution) {
264 (Driver::Transaction(td), true) => {
265 self.submit_with_checkpoint_race(td.clone(), request, client_addr, tx_digest)
266 .await?
267 }
268 (Driver::Transaction(td), false) => (
269 Some(
270 self.submit_with_transaction_driver(td.clone(), request, client_addr, false)
271 .await
272 .map_err(map_td_error_to_qd)?,
273 ),
274 None,
275 ),
276 (Driver::Quorum(qd), _) => {
277 let (_, qd_resp) = self
278 .execute_transaction_impl(qd, &epoch_store, request, client_addr)
279 .await?;
280 (Some(quorum_driver_response_to_v1(qd_resp)), None)
281 }
282 };
283
284 let needs_cache_rebuild = matches!(
293 response.as_ref().map(|r| &r.effects.finality_info),
294 None | Some(EffectsFinalityInfo::UncertifiedSingleValidator(_)),
295 );
296
297 let executed_locally = if !wait_for_local_execution {
298 false
299 } else if needs_cache_rebuild {
300 let Some(seq) = seq else {
301 return Err(QuorumDriverError::TimeoutBeforeFinality);
307 };
308 match response.as_mut() {
309 Some(existing) => Self::reconcile_effects_from_cache(
310 &self.validator_state,
311 tx_digest,
312 seq,
313 include_events,
314 include_input_objects,
315 include_output_objects,
316 existing,
317 &self.metrics,
318 )?,
319 None => {
320 response = Some(Self::build_response_from_cache(
321 &self.validator_state,
322 tx_digest,
323 seq,
324 include_events,
325 include_input_objects,
326 include_output_objects,
327 )?);
328 }
329 }
330 true
331 } else {
332 let ok = Self::wait_for_finalized_tx_executed_locally_with_timeout(
336 &self.validator_state,
337 &transaction,
338 &self.metrics,
339 )
340 .await
341 .is_ok();
342 add_server_timing("local_execution");
343 ok
344 };
345
346 let response = response.expect("response must be populated before return");
347
348 if matches!(
357 response.effects.finality_info,
358 EffectsFinalityInfo::UncertifiedSingleValidator(_)
359 ) {
360 debug_fatal!(
361 "Uncertified effects (UncertifiedSingleValidator) about to be returned \
362 to the client for tx {:?}",
363 response.effects.effects.transaction_digest()
364 );
365 return Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
366 "internal error: transaction effects not finalized".to_string(),
367 )));
368 }
369
370 Ok((response, executed_locally))
371 }
372
373 fn reconcile_effects_from_cache(
396 validator_state: &Arc<AuthorityState>,
397 tx_digest: TransactionDigest,
398 checkpoint_seq: CheckpointSequenceNumber,
399 include_events: bool,
400 include_input_objects: bool,
401 include_output_objects: bool,
402 response: &mut ExecuteTransactionResponseV1,
403 metrics: &TransactionOrchestratorMetrics,
404 ) -> Result<(), QuorumDriverError> {
405 let rebuilt = Self::build_response_from_cache(
406 validator_state,
407 tx_digest,
408 checkpoint_seq,
409 include_events,
410 include_input_objects,
411 include_output_objects,
412 )?;
413
414 let td_digest = response.effects.effects.digest();
415 let cache_digest = rebuilt.effects.effects.digest();
416 if td_digest != cache_digest {
417 warn!(
418 ?tx_digest,
419 ?td_digest,
420 ?cache_digest,
421 "reconcile_effects_from_cache: TransactionDriver and local cache disagree \
422 on effects digest — replacing with cache (possible byzantine submitter)"
423 );
424 }
425 if include_events && response.events.is_some() && rebuilt.events.is_none() {
426 warn!(
427 ?tx_digest,
428 "reconcile_effects_from_cache: submitter claimed events but cache has \
429 none — discarding (possible byzantine submitter)"
430 );
431 metrics.skip_effect_cert_events_cache_miss.inc();
432 }
433 *response = rebuilt;
434 Ok(())
435 }
436
437 fn build_response_from_cache(
445 validator_state: &Arc<AuthorityState>,
446 tx_digest: TransactionDigest,
447 checkpoint_seq: CheckpointSequenceNumber,
448 include_events: bool,
449 include_input_objects: bool,
450 include_output_objects: bool,
451 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
452 let cached = read_cached_transaction_data(
453 validator_state,
454 &tx_digest,
455 include_events,
456 include_input_objects,
457 include_output_objects,
458 )
459 .map_err(|e| {
460 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(format!(
461 "failed to read cached tx data for {tx_digest:?}: {e:?}"
462 )))
463 })?
464 .ok_or_else(|| {
465 warn!(
469 ?tx_digest,
470 "effects missing from cache after checkpoint inclusion — surfacing as \
471 TimeoutBeforeFinality"
472 );
473 QuorumDriverError::TimeoutBeforeFinality
474 })?;
475 let iota_types::transaction_executor::CachedTransactionData {
476 effects,
477 events,
478 input_objects,
479 output_objects,
480 } = cached;
481
482 let epoch = effects.epoch();
483 Ok(ExecuteTransactionResponseV1 {
484 effects: FinalizedEffects {
485 effects,
486 finality_info: EffectsFinalityInfo::Checkpointed(epoch, checkpoint_seq),
487 },
488 events,
489 input_objects,
490 output_objects,
491 auxiliary_data: None,
492 })
493 }
494
495 #[instrument(name = "tx_orchestrator_execute_transaction_v1", level = "trace", skip_all,
498 fields(tx_digest = ?request.transaction.digest()))]
499 pub async fn execute_transaction_v1(
500 &self,
501 request: ExecuteTransactionRequestV1,
502 skip_certification: bool,
503 client_addr: Option<SocketAddr>,
504 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
505 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
506
507 match &self.driver {
508 Driver::Transaction(td) => {
509 epoch_store
514 .verify_transaction(request.transaction.clone())
515 .map_err(QuorumDriverError::InvalidUserSignature)?;
516 self.submit_with_transaction_driver(
517 td.clone(),
518 request,
519 client_addr,
520 skip_certification,
521 )
522 .await
523 .map_err(map_td_error_to_qd)
524 }
525 Driver::Quorum(qd) => {
526 let qd_resp = self
527 .execute_transaction_impl(qd, &epoch_store, request, client_addr)
528 .await
529 .map(|(_, r)| r)?;
530 Ok(quorum_driver_response_to_v1(qd_resp))
531 }
532 }
533 }
534
535 #[instrument(name = "tx_orchestrator_submit_with_checkpoint_race", level = "trace", skip_all,
551 fields(tx_digest = ?tx_digest))]
552 async fn submit_with_checkpoint_race(
553 &self,
554 td: Arc<TransactionDriver<A>>,
555 request: ExecuteTransactionRequestV1,
556 client_addr: Option<SocketAddr>,
557 tx_digest: TransactionDigest,
558 ) -> Result<
559 (
560 Option<ExecuteTransactionResponseV1>,
561 Option<CheckpointSequenceNumber>,
562 ),
563 QuorumDriverError,
564 > {
565 let digests = [tx_digest];
566 let checkpoint_inclusion = self
567 .validator_state
568 .wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT);
569 tokio::pin!(checkpoint_inclusion);
570 let driver = self.submit_with_transaction_driver(td, request, client_addr, true);
571
572 let seq_for_tx = |inclusion_map: BTreeMap<_, (CheckpointSequenceNumber, _)>| {
573 inclusion_map.get(&tx_digest).map(|&(seq, _)| seq)
574 };
575
576 let result = tokio::select! {
577 biased;
578 driver_result = driver => {
583 let response = Some(driver_result.map_err(map_td_error_to_qd)?);
584 let seq = (&mut checkpoint_inclusion).await.ok().and_then(seq_for_tx);
585 (response, seq)
586 }
587 checkpoint_result = &mut checkpoint_inclusion => {
588 self.metrics.skip_effect_cert_checkpoint_overrode_driver.inc();
589 (None, checkpoint_result.ok().and_then(seq_for_tx))
590 }
591 };
592 add_server_timing("local_execution");
593 Ok(result)
594 }
595
596 #[instrument(name = "tx_orchestrator_submit_with_td", level = "trace", skip_all,
607 fields(tx_digest = ?request.transaction.digest()))]
608 async fn submit_with_transaction_driver(
609 &self,
610 td: Arc<TransactionDriver<A>>,
611 request: ExecuteTransactionRequestV1,
612 client_addr: Option<SocketAddr>,
613 skip_certification: bool,
614 ) -> Result<ExecuteTransactionResponseV1, TransactionDriverError> {
615 let tx_digest = *request.transaction.digest();
616
617 let td_response = td
630 .drive_transaction(
631 Some(request.transaction.clone()),
632 SubmitTransactionOptions {
633 forwarded_client_addr: client_addr,
634 ..Default::default()
635 },
636 Some(WAIT_FOR_FINALITY_TIMEOUT),
637 skip_certification,
638 )
639 .await?;
640
641 debug!(
642 "TransactionOrchestrator: TransactionDriver submission succeeded for transaction {}",
643 tx_digest
644 );
645
646 let QuorumTransactionResponse {
647 effects: td_effects,
648 events,
649 input_objects,
650 output_objects,
651 auxiliary_data,
652 } = td_response;
653
654 let effects = convert_td_to_qd_effects(td_effects);
655 Ok(ExecuteTransactionResponseV1 {
656 effects,
657 events: if request.include_events { events } else { None },
658 input_objects: if request.include_input_objects {
659 input_objects
660 } else {
661 None
662 },
663 output_objects: if request.include_output_objects {
664 output_objects
665 } else {
666 None
667 },
668 auxiliary_data: if request.include_auxiliary_data {
669 auxiliary_data
670 } else {
671 None
672 },
673 })
674 }
675
676 #[instrument(level = "trace", skip_all, fields(tx_digest = ?request.transaction.digest()))]
680 async fn execute_transaction_impl(
681 &self,
682 quorum_driver: &Arc<QuorumDriverHandler<A>>,
683 epoch_store: &Arc<AuthorityPerEpochStore>,
684 request: ExecuteTransactionRequestV1,
685 client_addr: Option<SocketAddr>,
686 ) -> Result<(VerifiedTransaction, QuorumDriverResponse), QuorumDriverError> {
687 request
690 .transaction
691 .validity_check(&epoch_store.tx_validity_check_context())
692 .map_err(QuorumDriverError::InvalidTransaction)?;
693 let transaction = epoch_store
694 .verify_transaction(request.transaction.clone())
695 .map_err(QuorumDriverError::InvalidUserSignature)?;
696 let (_in_flight_metrics_guards, good_response_metrics) = self.update_metrics(&transaction);
697 let tx_digest = *transaction.digest();
698 debug!(?tx_digest, "TO Received transaction execution request.");
699
700 let (_e2e_latency_timer, _txn_finality_timer) = if transaction.contains_shared_object() {
701 (
702 self.metrics.request_latency_shared_obj.start_timer(),
703 self.metrics
704 .wait_for_finality_latency_shared_obj
705 .start_timer(),
706 )
707 } else {
708 (
709 self.metrics.request_latency_single_writer.start_timer(),
710 self.metrics
711 .wait_for_finality_latency_single_writer
712 .start_timer(),
713 )
714 };
715
716 let wait_for_finality_gauge = self.metrics.wait_for_finality_in_flight.clone();
718 wait_for_finality_gauge.inc();
719 let _wait_for_finality_gauge = scopeguard::guard(wait_for_finality_gauge, |in_flight| {
720 in_flight.dec();
721 });
722
723 let ticket = self
724 .submit(
725 quorum_driver,
726 epoch_store.clone(),
727 transaction.clone(),
728 request,
729 client_addr,
730 )
731 .await
732 .map_err(|e| {
733 warn!(?tx_digest, "QuorumDriverInternalError: {e:?}");
734 QuorumDriverError::QuorumDriverInternal(e)
735 })?;
736
737 let Ok(result) = timeout(WAIT_FOR_FINALITY_TIMEOUT, ticket).await else {
738 debug!(?tx_digest, "Timeout waiting for transaction finality.");
739 self.metrics.wait_for_finality_timeout.inc();
740 return Err(QuorumDriverError::TimeoutBeforeFinality);
741 };
742 add_server_timing("wait_for_finality");
743
744 drop(_txn_finality_timer);
745 drop(_wait_for_finality_gauge);
746 self.metrics.wait_for_finality_finished.inc();
747
748 match result {
749 Err(err) => {
750 warn!(?tx_digest, "QuorumDriverInternalError: {err:?}");
751 Err(QuorumDriverError::QuorumDriverInternal(err))
752 }
753 Ok(Err(err)) => Err(err),
754 Ok(Ok(response)) => {
755 good_response_metrics.inc();
756 Ok((transaction, response))
757 }
758 }
759 }
760
761 #[instrument(name = "tx_orchestrator_submit", level = "trace", skip_all)]
764 async fn submit(
765 &self,
766 quorum_driver: &Arc<QuorumDriverHandler<A>>,
767 epoch_store: Arc<AuthorityPerEpochStore>,
768 transaction: VerifiedTransaction,
769 request: ExecuteTransactionRequestV1,
770 client_addr: Option<SocketAddr>,
771 ) -> IotaResult<impl Future<Output = IotaResult<QuorumDriverResult>> + '_> {
772 let tx_digest = *transaction.digest();
773 let ticket = self.notifier.register_one(&tx_digest);
774 if self
777 .pending_tx_log
778 .write_pending_transaction_maybe(&transaction)
779 .await?
780 {
781 debug!(?tx_digest, "no pending request in flight, submitting.");
782 quorum_driver
783 .submit_transaction_no_ticket(request.clone(), client_addr)
784 .await?;
785 }
786 let cache_reader = self.validator_state.get_transaction_cache_reader().clone();
792 let qd = quorum_driver.clone();
793 Ok(async move {
794 let digests = [tx_digest];
795 let effects_await = epoch_store
796 .within_alive_epoch(cache_reader.try_notify_read_executed_effects(&digests));
797 let res = match select(ticket, effects_await.boxed()).await {
799 Either::Left((quorum_driver_response, _)) => Ok(quorum_driver_response),
800 Either::Right((_, unfinished_quorum_driver_task)) => {
801 debug!(
802 ?tx_digest,
803 "Effects are available in DB, use quorum driver to get a certificate"
804 );
805 qd.submit_transaction_no_ticket(request, client_addr)
806 .await?;
807 Ok(unfinished_quorum_driver_task.await)
808 }
809 };
810 res
811 })
812 }
813
814 #[instrument(name = "tx_orchestrator_wait_for_finalized_tx_executed_locally_with_timeout", level = "debug", skip_all, fields(tx_digest = ?transaction.digest()), err)]
815 async fn wait_for_finalized_tx_executed_locally_with_timeout(
816 validator_state: &Arc<AuthorityState>,
817 transaction: &VerifiedTransaction,
818 metrics: &TransactionOrchestratorMetrics,
819 ) -> IotaResult {
820 let tx_digest = *transaction.digest();
821 metrics.local_execution_in_flight.inc();
822 let _metrics_guard =
823 scopeguard::guard(metrics.local_execution_in_flight.clone(), |in_flight| {
824 in_flight.dec();
825 });
826
827 let _guard = if transaction.contains_shared_object() {
828 metrics.local_execution_latency_shared_obj.start_timer()
829 } else {
830 metrics.local_execution_latency_single_writer.start_timer()
831 };
832 debug!(
833 ?tx_digest,
834 "Waiting for finalized tx to be executed locally."
835 );
836 match timeout(
837 LOCAL_EXECUTION_TIMEOUT,
838 validator_state
839 .get_transaction_cache_reader()
840 .try_notify_read_executed_effects_digests(&[tx_digest]),
841 )
842 .instrument(trace_span!("local_execution"))
843 .await
844 {
845 Err(_elapsed) => {
846 debug!(
847 ?tx_digest,
848 "Waiting for finalized tx to be executed locally timed out within {:?}.",
849 LOCAL_EXECUTION_TIMEOUT
850 );
851 metrics.local_execution_timeout.inc();
852 Err(IotaError::Timeout)
853 }
854 Ok(Err(err)) => {
855 debug!(
856 ?tx_digest,
857 "Waiting for finalized tx to be executed locally failed with error: {:?}", err
858 );
859 metrics.local_execution_failure.inc();
860 Err(IotaError::TransactionOrchestratorLocalExecution {
861 error: err.to_string(),
862 })
863 }
864 Ok(Ok(_)) => {
865 metrics.local_execution_success.inc();
866 Ok(())
867 }
868 }
869 }
870
871 async fn loop_pending_transaction_log(
873 mut effects_receiver: Receiver<QuorumDriverEffectsQueueResult>,
874 pending_transaction_log: Arc<WritePathPendingTransactionLog>,
875 ) {
876 loop {
877 match effects_receiver.recv().await {
878 Ok(Ok((transaction, ..))) => {
879 let tx_digest = transaction.digest();
880 if let Err(err) = pending_transaction_log.finish_transaction(tx_digest) {
881 error!(
882 ?tx_digest,
883 "Failed to finish transaction in pending transaction log: {err}"
884 );
885 }
886 }
887 Ok(Err((tx_digest, _err))) => {
888 if let Err(err) = pending_transaction_log.finish_transaction(&tx_digest) {
889 error!(
890 ?tx_digest,
891 "Failed to finish transaction in pending transaction log: {err}"
892 );
893 }
894 }
895 Err(RecvError::Closed) => {
896 error!("Sender of effects subscriber queue has been dropped!");
897 return;
898 }
899 Err(RecvError::Lagged(skipped_count)) => {
900 warn!("Skipped {skipped_count} transasctions in effects subscriber queue.");
901 }
902 }
903 }
904 }
905
906 pub fn quorum_driver(&self) -> Option<&Arc<QuorumDriverHandler<A>>> {
908 match &self.driver {
909 Driver::Quorum(handler) => Some(handler),
910 Driver::Transaction(_) => None,
911 }
912 }
913
914 pub fn clone_quorum_driver(&self) -> Option<Arc<QuorumDriverHandler<A>>> {
916 self.quorum_driver().cloned()
917 }
918
919 pub fn transaction_driver(&self) -> Option<&Arc<TransactionDriver<A>>> {
922 match &self.driver {
923 Driver::Quorum(_) => None,
924 Driver::Transaction(td) => Some(td),
925 }
926 }
927
928 pub fn clone_authority_aggregator(&self) -> Arc<AuthorityAggregator<A>> {
930 match &self.driver {
931 Driver::Quorum(qd) => qd.authority_aggregator().load_full(),
932 Driver::Transaction(td) => td.authority_aggregator().load_full(),
933 }
934 }
935
936 pub fn subscribe_to_effects_queue(&self) -> Option<Receiver<QuorumDriverEffectsQueueResult>> {
938 self.quorum_driver().map(|qd| qd.subscribe_to_effects())
939 }
940
941 fn update_metrics(
942 &'_ self,
943 transaction: &VerifiedTransaction,
944 ) -> (impl Drop, &'_ GenericCounter<AtomicU64>) {
945 let (in_flight, good_response) = if transaction.contains_shared_object() {
946 self.metrics.total_req_received_shared_object.inc();
947 (
948 self.metrics.req_in_flight_shared_object.clone(),
949 &self.metrics.good_response_shared_object,
950 )
951 } else {
952 self.metrics.total_req_received_single_writer.inc();
953 (
954 self.metrics.req_in_flight_single_writer.clone(),
955 &self.metrics.good_response_single_writer,
956 )
957 };
958 in_flight.inc();
959 (
960 scopeguard::guard(in_flight, |in_flight| {
961 in_flight.dec();
962 }),
963 good_response,
964 )
965 }
966
967 fn schedule_txes_in_log(
968 pending_tx_log: Arc<WritePathPendingTransactionLog>,
969 quorum_driver: Arc<QuorumDriverHandler<A>>,
970 ) {
971 spawn_logged_monitored_task!(async move {
972 if std::env::var("SKIP_LOADING_FROM_PENDING_TX_LOG").is_ok() {
973 info!("Skipping loading pending transactions from pending_tx_log.");
974 return;
975 }
976 let pending_txes = pending_tx_log
977 .load_all_pending_transactions()
978 .expect("failed to load all pending transactions");
979 info!(
980 "Recovering {} pending transactions from pending_tx_log.",
981 pending_txes.len()
982 );
983 for (i, tx) in pending_txes.into_iter().enumerate() {
984 let tx = tx.into_inner();
987 let tx_digest = *tx.digest();
988 if let Err(err) = quorum_driver
991 .submit_transaction_no_ticket(
992 ExecuteTransactionRequestV1 {
993 transaction: tx,
994 include_events: true,
995 include_input_objects: false,
996 include_output_objects: false,
997 include_auxiliary_data: false,
998 },
999 None,
1000 )
1001 .await
1002 {
1003 warn!(
1004 ?tx_digest,
1005 "Failed to enqueue transaction from pending_tx_log, err: {err:?}"
1006 );
1007 } else {
1008 debug!(?tx_digest, "Enqueued transaction from pending_tx_log");
1009 if (i + 1) % 1000 == 0 {
1010 info!("Enqueued {} transactions from pending_tx_log.", i + 1);
1011 }
1012 }
1013 }
1014 });
1018 }
1019
1020 pub fn load_all_pending_transactions(&self) -> IotaResult<Vec<VerifiedTransaction>> {
1021 self.pending_tx_log.load_all_pending_transactions()
1022 }
1023}
1024
1025fn quorum_driver_response_to_v1(response: QuorumDriverResponse) -> ExecuteTransactionResponseV1 {
1029 let QuorumDriverResponse {
1030 effects_cert,
1031 events,
1032 input_objects,
1033 output_objects,
1034 auxiliary_data,
1035 } = response;
1036 ExecuteTransactionResponseV1 {
1037 effects: FinalizedEffects::new_from_effects_cert(effects_cert.into()),
1038 events,
1039 input_objects,
1040 output_objects,
1041 auxiliary_data,
1042 }
1043}
1044
1045fn convert_td_to_qd_effects(td: TdFinalizedEffects) -> FinalizedEffects {
1048 let finality_info = match td.finality_info {
1049 TdEffectsFinalityInfo::Certified(sig) => EffectsFinalityInfo::Certified(sig),
1050 TdEffectsFinalityInfo::Checkpointed(epoch, seq) => {
1051 EffectsFinalityInfo::Checkpointed(epoch, seq)
1052 }
1053 TdEffectsFinalityInfo::QuorumExecuted(epoch) => EffectsFinalityInfo::QuorumExecuted(epoch),
1054 TdEffectsFinalityInfo::UncertifiedSingleValidator(epoch) => {
1055 EffectsFinalityInfo::UncertifiedSingleValidator(epoch)
1056 }
1057 };
1058 FinalizedEffects {
1059 effects: td.effects,
1060 finality_info,
1061 }
1062}
1063
1064fn map_td_error_to_qd(e: TransactionDriverError) -> QuorumDriverError {
1072 use TransactionDriverError::*;
1073 match e {
1074 ValidationFailed { error } => {
1075 QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature { error })
1076 }
1077 TimeoutWithLastRetriableError { .. } => QuorumDriverError::TimeoutBeforeFinality,
1078 RejectedByValidators {
1079 submission_non_retriable_errors,
1080 ..
1081 } => {
1082 let representative = submission_non_retriable_errors
1087 .errors
1088 .into_iter()
1089 .next()
1090 .map(|(msg, _, _, _)| msg)
1091 .unwrap_or_else(|| "transaction rejected as invalid during submission".to_string());
1092 QuorumDriverError::InvalidTransaction(IotaError::Unknown(format!(
1093 "Transaction was rejected as invalid by more than 1/3 of validator stake \
1094 during submission (non-retriable): {representative}"
1095 )))
1096 }
1097 Aborted {
1098 submission_retriable_errors,
1099 submission_non_retriable_errors,
1100 ..
1101 } => {
1102 let attempts = count_validator_attempts(&submission_retriable_errors)
1107 + count_validator_attempts(&submission_non_retriable_errors);
1108 QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
1109 total_attempts: attempts,
1110 }
1111 }
1112 other @ ForkedExecution { .. } => {
1113 let msg = other.to_string();
1117 error!("TransactionDriver observed forked execution: {msg}");
1118 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1119 }
1120 other @ ClientInternal { .. } => {
1121 let msg = other.to_string();
1122 warn!("TransactionDriver client-internal error: {msg}");
1123 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1124 }
1125 other @ SubmittedButFetchFailed { .. } => {
1126 let msg = other.to_string();
1127 warn!("TransactionDriver submitted transaction but failed to fetch effects: {msg}");
1128 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1129 }
1130 }
1131}
1132
1133fn count_validator_attempts(errors: &AggregatedRequestErrors) -> u32 {
1134 errors
1135 .errors
1136 .iter()
1137 .map(|(_, authorities, _, _)| authorities.len() as u32)
1138 .sum()
1139}
1140
1141#[derive(Clone)]
1143pub struct TransactionOrchestratorMetrics {
1144 total_req_received_single_writer: GenericCounter<AtomicU64>,
1145 total_req_received_shared_object: GenericCounter<AtomicU64>,
1146
1147 good_response_single_writer: GenericCounter<AtomicU64>,
1148 good_response_shared_object: GenericCounter<AtomicU64>,
1149
1150 req_in_flight_single_writer: GenericGauge<AtomicI64>,
1151 req_in_flight_shared_object: GenericGauge<AtomicI64>,
1152
1153 wait_for_finality_in_flight: GenericGauge<AtomicI64>,
1154 wait_for_finality_finished: GenericCounter<AtomicU64>,
1155 wait_for_finality_timeout: GenericCounter<AtomicU64>,
1156
1157 local_execution_in_flight: GenericGauge<AtomicI64>,
1158 local_execution_success: GenericCounter<AtomicU64>,
1159 local_execution_timeout: GenericCounter<AtomicU64>,
1160 local_execution_failure: GenericCounter<AtomicU64>,
1161
1162 skip_effect_cert_events_cache_miss: GenericCounter<AtomicU64>,
1167
1168 skip_effect_cert_checkpoint_overrode_driver: GenericCounter<AtomicU64>,
1174
1175 request_latency_single_writer: Histogram,
1176 request_latency_shared_obj: Histogram,
1177 wait_for_finality_latency_single_writer: Histogram,
1178 wait_for_finality_latency_shared_obj: Histogram,
1179 local_execution_latency_single_writer: Histogram,
1180 local_execution_latency_shared_obj: Histogram,
1181}
1182
1183impl TransactionOrchestratorMetrics {
1187 pub fn new(registry: &Registry) -> Self {
1188 let total_req_received = register_int_counter_vec_with_registry!(
1189 "tx_orchestrator_total_req_received",
1190 "Total number of executions request Transaction Orchestrator receives, group by tx type",
1191 &["tx_type"],
1192 registry
1193 )
1194 .unwrap();
1195
1196 let total_req_received_single_writer =
1197 total_req_received.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1198 let total_req_received_shared_object =
1199 total_req_received.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1200
1201 let good_response = register_int_counter_vec_with_registry!(
1202 "tx_orchestrator_good_response",
1203 "Total number of good responses Transaction Orchestrator generates, group by tx type",
1204 &["tx_type"],
1205 registry
1206 )
1207 .unwrap();
1208
1209 let good_response_single_writer =
1210 good_response.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1211 let good_response_shared_object = good_response.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1212
1213 let req_in_flight = register_int_gauge_vec_with_registry!(
1214 "tx_orchestrator_req_in_flight",
1215 "Number of requests in flights Transaction Orchestrator processes, group by tx type",
1216 &["tx_type"],
1217 registry
1218 )
1219 .unwrap();
1220
1221 let req_in_flight_single_writer =
1222 req_in_flight.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1223 let req_in_flight_shared_object = req_in_flight.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1224
1225 let request_latency = register_histogram_vec_with_registry!(
1226 "tx_orchestrator_request_latency",
1227 "Time spent in processing one Transaction Orchestrator request",
1228 &["tx_type"],
1229 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1230 registry,
1231 )
1232 .unwrap();
1233 let wait_for_finality_latency = register_histogram_vec_with_registry!(
1234 "tx_orchestrator_wait_for_finality_latency",
1235 "Time spent in waiting for one Transaction Orchestrator request gets finalized",
1236 &["tx_type"],
1237 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1238 registry,
1239 )
1240 .unwrap();
1241 let local_execution_latency = register_histogram_vec_with_registry!(
1242 "tx_orchestrator_local_execution_latency",
1243 "Time spent in waiting for one Transaction Orchestrator gets locally executed",
1244 &["tx_type"],
1245 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1246 registry,
1247 )
1248 .unwrap();
1249
1250 Self {
1251 total_req_received_single_writer,
1252 total_req_received_shared_object,
1253 good_response_single_writer,
1254 good_response_shared_object,
1255 req_in_flight_single_writer,
1256 req_in_flight_shared_object,
1257 wait_for_finality_in_flight: register_int_gauge_with_registry!(
1258 "tx_orchestrator_wait_for_finality_in_flight",
1259 "Number of in flight txns Transaction Orchestrator are waiting for finality for",
1260 registry,
1261 )
1262 .unwrap(),
1263 wait_for_finality_finished: register_int_counter_with_registry!(
1264 "tx_orchestrator_wait_for_finality_finished",
1265 "Total number of txns Transaction Orchestrator gets responses from Quorum Driver before timeout, either success or failure",
1266 registry,
1267 )
1268 .unwrap(),
1269 wait_for_finality_timeout: register_int_counter_with_registry!(
1270 "tx_orchestrator_wait_for_finality_timeout",
1271 "Total number of txns timing out in waiting for finality Transaction Orchestrator handles",
1272 registry,
1273 )
1274 .unwrap(),
1275 local_execution_in_flight: register_int_gauge_with_registry!(
1276 "tx_orchestrator_local_execution_in_flight",
1277 "Number of local execution txns in flights Transaction Orchestrator handles",
1278 registry,
1279 )
1280 .unwrap(),
1281 local_execution_success: register_int_counter_with_registry!(
1282 "tx_orchestrator_local_execution_success",
1283 "Total number of successful local execution txns Transaction Orchestrator handles",
1284 registry,
1285 )
1286 .unwrap(),
1287 local_execution_timeout: register_int_counter_with_registry!(
1288 "tx_orchestrator_local_execution_timeout",
1289 "Total number of timed-out local execution txns Transaction Orchestrator handles",
1290 registry,
1291 )
1292 .unwrap(),
1293 local_execution_failure: register_int_counter_with_registry!(
1294 "tx_orchestrator_local_execution_failure",
1295 "Total number of failed local execution txns Transaction Orchestrator handles",
1296 registry,
1297 )
1298 .unwrap(),
1299 skip_effect_cert_events_cache_miss: register_int_counter_with_registry!(
1300 "tx_orchestrator_skip_effect_cert_events_cache_miss",
1301 "Number of skip-effect-certification responses rejected because the \
1302 single submitter claimed to have events but the local cache did not \
1303 corroborate them",
1304 registry,
1305 )
1306 .unwrap(),
1307 skip_effect_cert_checkpoint_overrode_driver: register_int_counter_with_registry!(
1308 "tx_orchestrator_skip_effect_cert_checkpoint_overrode_driver",
1309 "Number of skip-effect-certification requests where local checkpoint \
1310 inclusion completed before the TransactionDriver call returned; the \
1311 driver future was cancelled and the response was rebuilt from cache",
1312 registry,
1313 )
1314 .unwrap(),
1315 request_latency_single_writer: request_latency
1316 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1317 request_latency_shared_obj: request_latency.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1318 wait_for_finality_latency_single_writer: wait_for_finality_latency
1319 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1320 wait_for_finality_latency_shared_obj: wait_for_finality_latency
1321 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1322 local_execution_latency_single_writer: local_execution_latency
1323 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1324 local_execution_latency_shared_obj: local_execution_latency
1325 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1326 }
1327 }
1328
1329 pub fn new_for_tests() -> Self {
1330 let registry = Registry::new();
1331 Self::new(®istry)
1332 }
1333}
1334
1335#[async_trait::async_trait]
1336impl<A> iota_types::transaction_executor::TransactionExecutor for TransactionOrchestrator<A>
1337where
1338 A: AuthorityAPI + Send + Sync + 'static + Clone,
1339{
1340 async fn execute_transaction(
1341 &self,
1342 request: ExecuteTransactionRequestV1,
1343 skip_certification: bool,
1344 client_addr: Option<std::net::SocketAddr>,
1345 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
1346 self.execute_transaction_v1(request, skip_certification, client_addr)
1347 .await
1348 }
1349
1350 fn simulate_transaction(
1351 &self,
1352 transaction: TransactionData,
1353 checks: VmChecks,
1354 ) -> Result<SimulateTransactionResult, IotaError> {
1355 self.validator_state
1356 .simulate_transaction(transaction, checks)
1357 }
1358
1359 async fn wait_for_checkpoint_inclusion(
1366 &self,
1367 digests: &[TransactionDigest],
1368 timeout: Duration,
1369 ) -> Result<BTreeMap<TransactionDigest, (CheckpointSequenceNumber, u64)>, IotaError> {
1370 self.validator_state
1371 .wait_for_checkpoint_inclusion(digests, timeout)
1372 .await
1373 }
1374
1375 fn read_transaction_from_cache(
1376 &self,
1377 digest: &TransactionDigest,
1378 include_events: bool,
1379 include_input_objects: bool,
1380 include_output_objects: bool,
1381 ) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1382 read_cached_transaction_data(
1383 &self.validator_state,
1384 digest,
1385 include_events,
1386 include_input_objects,
1387 include_output_objects,
1388 )
1389 }
1390}
1391
1392fn read_cached_transaction_data(
1397 validator_state: &Arc<AuthorityState>,
1398 digest: &TransactionDigest,
1399 include_events: bool,
1400 include_input_objects: bool,
1401 include_output_objects: bool,
1402) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1403 let cache = validator_state.get_transaction_cache_reader();
1404 let Some(effects) = cache.try_get_executed_effects(digest)? else {
1405 return Ok(None);
1406 };
1407
1408 let events = if include_events {
1409 cache.try_get_events(digest)?
1410 } else {
1411 None
1412 };
1413
1414 let input_objects = if include_input_objects {
1415 Some(
1416 validator_state
1417 .get_transaction_input_objects(&effects)
1418 .map_err(|e| IotaError::Unknown(format!("input objects: {e:?}")))?,
1419 )
1420 } else {
1421 None
1422 };
1423 let output_objects = if include_output_objects {
1424 Some(
1425 validator_state
1426 .get_transaction_output_objects(&effects)
1427 .map_err(|e| IotaError::Unknown(format!("output objects: {e:?}")))?,
1428 )
1429 } else {
1430 None
1431 };
1432
1433 Ok(Some(
1434 iota_types::transaction_executor::CachedTransactionData {
1435 effects,
1436 events,
1437 input_objects,
1438 output_objects,
1439 },
1440 ))
1441}