1use std::{
11 collections::{BTreeMap, HashMap, hash_map::Entry},
12 net::SocketAddr,
13 ops::Deref,
14 path::Path,
15 sync::Arc,
16 time::Duration,
17};
18
19use futures::{
20 FutureExt,
21 future::{Either, Future, select},
22};
23use iota_common::{debug_fatal, sync::notify_read::NotifyRead};
24use iota_config::NodeConfig;
25use iota_metrics::{
26 TX_TYPE_SHARED_OBJ_TX, TX_TYPE_SINGLE_WRITER_TX, add_server_timing,
27 spawn_logged_monitored_task, spawn_monitored_task,
28};
29use iota_sdk_types::{Transaction, TransactionDigest};
30use iota_storage::write_path_pending_tx_log::WritePathPendingTransactionLog;
31use iota_types::{
32 effects::TransactionEffectsAPI,
33 error::{IotaError, IotaResult},
34 iota_system_state::IotaSystemState,
35 messages_checkpoint::CheckpointSequenceNumber,
36 quorum_driver_types::{
37 EffectsFinalityInfo, ExecuteTransactionRequestType, ExecuteTransactionRequestV1,
38 ExecuteTransactionResponseV1, FinalizedEffects, IsTransactionExecutedLocally,
39 QuorumDriverEffectsQueueResult, QuorumDriverError, QuorumDriverResponse,
40 QuorumDriverResult,
41 },
42 transaction::{SenderSignedTransactionAPI, VerifiedTransaction},
43 transaction_driver_types::{
44 EffectsFinalityInfo as TdEffectsFinalityInfo, FinalizedEffects as TdFinalizedEffects,
45 },
46 transaction_executor::{SimulateTransactionResult, VmChecks},
47};
48use parking_lot::Mutex;
49use prometheus_filtered::{
50 Histogram, MetricLevel, Registry,
51 core::{AtomicI64, AtomicU64, GenericCounter, GenericGauge},
52 register_histogram_vec_with_registry, register_int_counter_vec_with_registry,
53 register_int_counter_with_registry, register_int_gauge_vec_with_registry,
54 register_int_gauge_with_registry,
55};
56use tokio::{
57 sync::{
58 broadcast::{Receiver, error::RecvError},
59 watch,
60 },
61 task::JoinHandle,
62 time::timeout,
63};
64use tracing::{Instrument, debug, error, info, instrument, trace_span, warn};
65
66use crate::{
67 authority::{AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore},
68 authority_aggregator::AuthorityAggregator,
69 authority_client::{AuthorityAPI, NetworkAuthorityClient},
70 quorum_driver::{
71 QuorumDriverHandler, QuorumDriverHandlerBuilder, QuorumDriverMetrics,
72 reconfig_observer::{OnsiteReconfigObserver, ReconfigObserver},
73 },
74 transaction_driver::{
75 AggregatedRequestErrors, QuorumTransactionResponse, SubmitTransactionOptions,
76 TransactionDriver, TransactionDriverError, TransactionDriverMetrics,
77 reconfig_observer::OnsiteReconfigObserver as TdOnsiteReconfigObserver,
78 },
79 validator_client_monitor::ValidatorClientMetrics,
80};
81
82const LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(10);
85
86const WAIT_FOR_FINALITY_TIMEOUT: Duration = Duration::from_secs(30);
87
88enum Driver<A: Clone> {
91 Quorum(Arc<QuorumDriverHandler<A>>),
93 Transaction(Arc<TransactionDriver<A>>),
95}
96
97pub struct TransactionOrchestrator<A: Clone> {
108 quorum_driver: Option<Arc<QuorumDriverHandler<A>>>,
109 transaction_driver: Arc<TransactionDriver<A>>,
110 validator_state: Arc<AuthorityState>,
111 _local_executor_handle: Option<JoinHandle<()>>,
114 pending_tx_log: Arc<WritePathPendingTransactionLog>,
115 in_flight_transactions: InFlightTransactions,
123 notifier: Arc<NotifyRead<TransactionDigest, QuorumDriverResult>>,
124 metrics: Arc<TransactionOrchestratorMetrics>,
125}
126
127impl TransactionOrchestrator<NetworkAuthorityClient> {
128 pub fn new_with_auth_aggregator(
129 validators: Arc<AuthorityAggregator<NetworkAuthorityClient>>,
130 validator_state: Arc<AuthorityState>,
131 reconfig_channel: Receiver<IotaSystemState>,
132 parent_path: &Path,
133 prometheus_registry: &Registry,
134 node_config: Option<&NodeConfig>,
135 ) -> Self {
136 let td_reconfig_observer = TdOnsiteReconfigObserver::new(
137 reconfig_channel.resubscribe(),
138 validator_state.get_object_cache_reader().clone(),
139 validator_state.clone_committee_store(),
140 validators.safe_client_metrics_base.clone(),
141 );
142
143 let qd_reconfig_observer = OnsiteReconfigObserver::new(
144 reconfig_channel.resubscribe(),
145 validator_state.get_object_cache_reader().clone(),
146 validator_state.clone_committee_store(),
147 validators.safe_client_metrics_base.clone(),
148 validators.metrics.deref().clone(),
149 );
150
151 TransactionOrchestrator::new(
152 validators,
153 validator_state,
154 parent_path,
155 prometheus_registry,
156 qd_reconfig_observer,
157 td_reconfig_observer,
158 node_config,
159 )
160 }
161}
162
163impl<A> TransactionOrchestrator<A>
164where
165 A: AuthorityAPI + Send + Sync + 'static + Clone,
166 OnsiteReconfigObserver: ReconfigObserver<A>,
167 TdOnsiteReconfigObserver: crate::transaction_driver::reconfig_observer::ReconfigObserver<A>,
168{
169 pub fn new(
170 validators: Arc<AuthorityAggregator<A>>,
171 validator_state: Arc<AuthorityState>,
172 parent_path: &Path,
173 prometheus_registry: &Registry,
174 reconfig_observer: OnsiteReconfigObserver,
175 td_reconfig_observer: TdOnsiteReconfigObserver,
176 node_config: Option<&NodeConfig>,
177 ) -> Self {
178 let epoch_store = validator_state.load_epoch_store_one_call_per_task();
179 let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
180
181 let notifier = Arc::new(NotifyRead::new());
182 let metrics = Arc::new(TransactionOrchestratorMetrics::new(prometheus_registry));
183 let pending_tx_log = Arc::new(WritePathPendingTransactionLog::new(
184 parent_path.join("fullnode_pending_transactions"),
185 ));
186
187 let quorum_driver_metrics = Arc::new(QuorumDriverMetrics::new(prometheus_registry));
190 let transaction_driver_metrics =
191 Arc::new(TransactionDriverMetrics::new(prometheus_registry));
192 let client_metrics = Arc::new(ValidatorClientMetrics::new(prometheus_registry));
193
194 let (quorum_driver, _local_executor_handle) = if use_transaction_driver {
195 (None, None)
196 } else {
197 let quorum_driver = Arc::new(
198 QuorumDriverHandlerBuilder::new(validators.clone(), quorum_driver_metrics)
199 .with_notifier(notifier.clone())
200 .with_reconfig_observer(Arc::new(reconfig_observer))
201 .start(),
202 );
203 let effects_receiver = quorum_driver.subscribe_to_effects();
207 let pending_tx_log_clone = pending_tx_log.clone();
208 let local_executor_handle = spawn_monitored_task!(async move {
209 Self::loop_pending_transaction_log(effects_receiver, pending_tx_log_clone).await;
210 });
211 Self::schedule_txes_in_log(pending_tx_log.clone(), quorum_driver.clone());
212 (Some(quorum_driver), Some(local_executor_handle))
213 };
214
215 let pcool_flow_enabled: Arc<dyn Fn() -> bool + Send + Sync> = {
217 let validator_state = Arc::downgrade(&validator_state);
218 Arc::new(move || {
219 validator_state.upgrade().is_some_and(|state| {
220 state
221 .load_epoch_store_one_call_per_task()
222 .protocol_config()
223 .enable_pcool_flow()
224 })
225 })
226 };
227 let transaction_driver = TransactionDriver::new(
228 validators,
229 Arc::new(td_reconfig_observer),
230 transaction_driver_metrics,
231 node_config.and_then(|config| config.validator_client_monitor_config.clone()),
232 client_metrics,
233 pcool_flow_enabled,
234 );
235
236 Self {
237 quorum_driver,
238 transaction_driver,
239 validator_state,
240 _local_executor_handle,
241 pending_tx_log,
242 in_flight_transactions: Default::default(),
243 notifier,
244 metrics,
245 }
246 }
247}
248
249impl<A> TransactionOrchestrator<A>
250where
251 A: AuthorityAPI + Send + Sync + 'static + Clone,
252{
253 fn select_driver(
259 &self,
260 epoch_store: &AuthorityPerEpochStore,
261 ) -> Result<Driver<A>, QuorumDriverError> {
262 if epoch_store.protocol_config().enable_pcool_flow() {
263 return Ok(Driver::Transaction(self.transaction_driver.clone()));
264 }
265 self.quorum_driver
266 .clone()
267 .map(Driver::Quorum)
268 .ok_or_else(|| {
269 error!(
270 "This fullnode started while P-COOL was enabled and must be restarted to \
271 serve the certificate-based flow"
272 );
273 QuorumDriverError::QuorumDriverInternal(IotaError::UnsupportedFeature {
274 error: "this fullnode started while P-COOL was enabled and must be \
275 restarted to serve the certificate-based flow"
276 .to_string(),
277 })
278 })
279 }
280
281 #[instrument(name = "tx_orchestrator_execute_transaction_block", level = "trace", skip_all,
282 fields(
283 tx_digest = ?request.transaction.digest(),
284 tx_type = ?request_type,
285 ),
286 err)]
287 pub async fn execute_transaction_block(
288 &self,
289 request: ExecuteTransactionRequestV1,
290 request_type: ExecuteTransactionRequestType,
291 client_addr: Option<SocketAddr>,
292 ) -> Result<(ExecuteTransactionResponseV1, IsTransactionExecutedLocally), QuorumDriverError>
293 {
294 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
295
296 let transaction = epoch_store
297 .verify_transaction(request.transaction.clone())
298 .map_err(QuorumDriverError::InvalidUserSignature)?;
299
300 let include_events = request.include_events;
305 let include_input_objects = request.include_input_objects;
306 let include_output_objects = request.include_output_objects;
307
308 let tx_digest = *transaction.digest();
309
310 if let Some(response) = Self::build_response_from_local_effects(
314 &self.validator_state,
315 &tx_digest,
316 include_events,
317 include_input_objects,
318 include_output_objects,
319 )? {
320 self.metrics.early_cached_response.inc();
321 debug!(
322 ?tx_digest,
323 "Returning cached results for already-executed transaction"
324 );
325 return Ok((response, true));
326 }
327
328 transaction
334 .validity_check(&epoch_store.tx_validity_check_context())
335 .map_err(QuorumDriverError::InvalidTransaction)?;
336
337 let wait_for_local_execution = matches!(
338 request_type,
339 ExecuteTransactionRequestType::WaitForLocalExecution
340 );
341 let (mut response, seq) =
342 match (self.select_driver(&epoch_store)?, wait_for_local_execution) {
343 (Driver::Transaction(td), true) => {
344 let in_flight_transactions = self.in_flight_transactions.clone();
345 let validator_state = self.validator_state.clone();
346 let metrics = self.metrics.clone();
347 join_submission_task(spawn_monitored_task!(Self::submit_with_checkpoint_race(
351 td,
352 in_flight_transactions,
353 validator_state,
354 metrics,
355 request,
356 client_addr,
357 tx_digest,
358 )))
359 .await?
360 }
361 (Driver::Transaction(td), false) => {
362 let in_flight_transactions = self.in_flight_transactions.clone();
363 let validator_state = self.validator_state.clone();
364 let result = join_submission_task(spawn_monitored_task!(
366 Self::submit_with_transaction_driver(
367 td,
368 in_flight_transactions,
369 validator_state,
370 request,
371 client_addr,
372 false,
373 )
374 ))
375 .await?;
376 (Some(result), None)
377 }
378 (Driver::Quorum(qd), _) => {
379 let qd_resp = self
380 .execute_transaction_impl(
381 &qd,
382 &epoch_store,
383 request,
384 transaction.clone(),
385 client_addr,
386 )
387 .await?;
388 (Some(quorum_driver_response_to_v1(qd_resp)), None)
389 }
390 };
391
392 let needs_cache_rebuild = matches!(
403 response.as_ref().map(|r| &r.effects.finality_info),
404 None | Some(EffectsFinalityInfo::UncertifiedSingleValidator(_)),
405 );
406
407 let executed_locally = if !wait_for_local_execution {
408 false
409 } else if needs_cache_rebuild {
410 let Some(seq) = seq else {
411 return Err(QuorumDriverError::TimeoutBeforeFinality);
417 };
418 match response.as_mut() {
419 Some(existing) => Self::reconcile_effects_from_cache(
420 &self.validator_state,
421 tx_digest,
422 seq,
423 include_events,
424 include_input_objects,
425 include_output_objects,
426 existing,
427 &self.metrics,
428 )?,
429 None => {
430 response = Some(Self::build_response_from_cache(
431 &self.validator_state,
432 tx_digest,
433 seq,
434 include_events,
435 include_input_objects,
436 include_output_objects,
437 )?);
438 }
439 }
440 true
441 } else {
442 let ok = Self::wait_for_finalized_tx_executed_locally_with_timeout(
447 &self.validator_state,
448 &transaction,
449 &self.metrics,
450 )
451 .await
452 .is_ok();
453 add_server_timing("local_execution");
454 ok
455 };
456
457 let response = response.expect("response must be populated before return");
458
459 if matches!(
468 response.effects.finality_info,
469 EffectsFinalityInfo::UncertifiedSingleValidator(_)
470 ) {
471 debug_fatal!(
472 "Uncertified effects (UncertifiedSingleValidator) about to be returned \
473 to the client for tx {:?}",
474 response.effects.effects.transaction_digest()
475 );
476 return Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
477 "internal error: transaction effects not finalized".to_string(),
478 )));
479 }
480
481 Ok((response, executed_locally))
482 }
483
484 fn reconcile_effects_from_cache(
507 validator_state: &Arc<AuthorityState>,
508 tx_digest: TransactionDigest,
509 checkpoint_seq: CheckpointSequenceNumber,
510 include_events: bool,
511 include_input_objects: bool,
512 include_output_objects: bool,
513 response: &mut ExecuteTransactionResponseV1,
514 metrics: &TransactionOrchestratorMetrics,
515 ) -> Result<(), QuorumDriverError> {
516 let rebuilt = Self::build_response_from_cache(
517 validator_state,
518 tx_digest,
519 checkpoint_seq,
520 include_events,
521 include_input_objects,
522 include_output_objects,
523 )?;
524
525 let td_digest = response.effects.effects.digest();
526 let cache_digest = rebuilt.effects.effects.digest();
527 if td_digest != cache_digest {
528 warn!(
529 ?tx_digest,
530 ?td_digest,
531 ?cache_digest,
532 "reconcile_effects_from_cache: TransactionDriver and local cache disagree \
533 on effects digest — replacing with cache (possible byzantine submitter)"
534 );
535 }
536 if include_events && response.events.is_some() && rebuilt.events.is_none() {
537 warn!(
538 ?tx_digest,
539 "reconcile_effects_from_cache: submitter claimed events but cache has \
540 none — discarding (possible byzantine submitter)"
541 );
542 metrics.skip_effect_cert_events_cache_miss.inc();
543 }
544 *response = rebuilt;
545 Ok(())
546 }
547
548 fn build_response_from_cache(
556 validator_state: &Arc<AuthorityState>,
557 tx_digest: TransactionDigest,
558 checkpoint_seq: CheckpointSequenceNumber,
559 include_events: bool,
560 include_input_objects: bool,
561 include_output_objects: bool,
562 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
563 let cached = read_cached_transaction_data(
564 validator_state,
565 &tx_digest,
566 include_events,
567 include_input_objects,
568 include_output_objects,
569 )
570 .map_err(|e| {
571 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(format!(
572 "failed to read cached tx data for {tx_digest:?}: {e:?}"
573 )))
574 })?
575 .ok_or_else(|| {
576 warn!(
580 ?tx_digest,
581 "effects missing from cache after checkpoint inclusion — surfacing as \
582 TimeoutBeforeFinality"
583 );
584 QuorumDriverError::TimeoutBeforeFinality
585 })?;
586 let iota_types::transaction_executor::CachedTransactionData {
587 effects,
588 events,
589 input_objects,
590 output_objects,
591 } = cached;
592
593 let epoch = effects.epoch();
594 Ok(ExecuteTransactionResponseV1 {
595 effects: FinalizedEffects {
596 effects,
597 finality_info: EffectsFinalityInfo::Checkpointed(epoch, checkpoint_seq),
598 },
599 events,
600 input_objects,
601 output_objects,
602 auxiliary_data: None,
603 })
604 }
605
606 fn build_response_from_local_effects(
613 validator_state: &Arc<AuthorityState>,
614 tx_digest: &TransactionDigest,
615 include_events: bool,
616 include_input_objects: bool,
617 include_output_objects: bool,
618 ) -> Result<Option<ExecuteTransactionResponseV1>, QuorumDriverError> {
619 let Some(cached) = read_cached_transaction_data(
620 validator_state,
621 tx_digest,
622 include_events,
623 include_input_objects,
624 include_output_objects,
625 )
626 .map_err(QuorumDriverError::QuorumDriverInternal)?
627 else {
628 return Ok(None);
629 };
630 let iota_types::transaction_executor::CachedTransactionData {
631 effects,
632 events,
633 input_objects,
634 output_objects,
635 } = cached;
636
637 let epoch = effects.epoch();
638 Ok(Some(ExecuteTransactionResponseV1 {
639 effects: FinalizedEffects {
640 effects,
641 finality_info: EffectsFinalityInfo::QuorumExecuted(epoch),
642 },
643 events,
644 input_objects,
645 output_objects,
646 auxiliary_data: None,
647 }))
648 }
649
650 #[instrument(name = "tx_orchestrator_execute_transaction_v1", level = "trace", skip_all,
653 fields(tx_digest = ?request.transaction.digest()))]
654 pub async fn execute_transaction_v1(
655 &self,
656 request: ExecuteTransactionRequestV1,
657 skip_certification: bool,
658 client_addr: Option<SocketAddr>,
659 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
660 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
661
662 let transaction = epoch_store
663 .verify_transaction(request.transaction.clone())
664 .map_err(QuorumDriverError::InvalidUserSignature)?;
665 let tx_digest = *transaction.digest();
666
667 if let Some(response) = Self::build_response_from_local_effects(
671 &self.validator_state,
672 &tx_digest,
673 request.include_events,
674 request.include_input_objects,
675 request.include_output_objects,
676 )? {
677 self.metrics.early_cached_response.inc();
678 debug!(
679 ?tx_digest,
680 "Returning cached results for already-executed transaction"
681 );
682 return Ok(response);
683 }
684
685 transaction
691 .validity_check(&epoch_store.tx_validity_check_context())
692 .map_err(QuorumDriverError::InvalidTransaction)?;
693
694 match self.select_driver(&epoch_store)? {
695 Driver::Transaction(td) => {
696 let in_flight_transactions = self.in_flight_transactions.clone();
697 let validator_state = self.validator_state.clone();
698 join_submission_task(spawn_monitored_task!(Self::submit_with_transaction_driver(
706 td,
707 in_flight_transactions,
708 validator_state,
709 request,
710 client_addr,
711 skip_certification,
712 )))
713 .await
714 }
715 Driver::Quorum(qd) => {
716 let qd_resp = self
717 .execute_transaction_impl(&qd, &epoch_store, request, transaction, client_addr)
718 .await?;
719 Ok(quorum_driver_response_to_v1(qd_resp))
720 }
721 }
722 }
723
724 #[instrument(name = "tx_orchestrator_submit_with_checkpoint_race", level = "trace", skip_all,
743 fields(tx_digest = ?tx_digest))]
744 async fn submit_with_checkpoint_race(
745 td: Arc<TransactionDriver<A>>,
746 in_flight_transactions: InFlightTransactions,
747 validator_state: Arc<AuthorityState>,
748 metrics: Arc<TransactionOrchestratorMetrics>,
749 request: ExecuteTransactionRequestV1,
750 client_addr: Option<SocketAddr>,
751 tx_digest: TransactionDigest,
752 ) -> Result<
753 (
754 Option<ExecuteTransactionResponseV1>,
755 Option<CheckpointSequenceNumber>,
756 ),
757 QuorumDriverError,
758 > {
759 let digests = [tx_digest];
760 let checkpoint_inclusion =
761 validator_state.wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT);
762 tokio::pin!(checkpoint_inclusion);
763 let driver = Self::submit_with_transaction_driver(
764 td,
765 in_flight_transactions,
766 validator_state.clone(),
767 request,
768 client_addr,
769 true,
770 );
771
772 let seq_for_tx = |inclusion_map: BTreeMap<_, (CheckpointSequenceNumber, _)>| {
773 inclusion_map.get(&tx_digest).map(|&(seq, _)| seq)
774 };
775
776 let result = tokio::select! {
777 biased;
778 driver_result = driver => {
783 let response = Some(driver_result?);
784 let seq = (&mut checkpoint_inclusion).await.ok().and_then(seq_for_tx);
785 (response, seq)
786 }
787 checkpoint_result = &mut checkpoint_inclusion => {
788 metrics.skip_effect_cert_checkpoint_overrode_driver.inc();
789 let seq = checkpoint_result.ok().and_then(seq_for_tx);
794 (None, seq)
795 }
796 };
797 add_server_timing("local_execution");
798 Ok(result)
799 }
800
801 #[instrument(name = "tx_orchestrator_submit_with_td", level = "trace", skip_all,
815 fields(tx_digest = ?request.transaction.digest()))]
816 async fn submit_with_transaction_driver(
817 td: Arc<TransactionDriver<A>>,
818 in_flight_transactions: InFlightTransactions,
819 validator_state: Arc<AuthorityState>,
820 request: ExecuteTransactionRequestV1,
821 client_addr: Option<SocketAddr>,
822 skip_certification: bool,
823 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
824 let tx_digest = *request.transaction.digest();
825
826 let guard = match TransactionSubmissionGuard::acquire(in_flight_transactions, tx_digest) {
832 TransactionSubmission::Driving(guard) => guard,
833 TransactionSubmission::AlreadyInFlight(receiver) => {
834 debug!(
835 ?tx_digest,
836 "transaction already in flight; awaiting its outcome instead of driving a \
837 duplicate submission"
838 );
839 return Self::await_in_flight_transaction(
840 receiver,
841 &td,
842 &validator_state,
843 tx_digest,
844 &request,
845 client_addr,
846 skip_certification,
847 )
848 .await;
849 }
850 };
851
852 let td_response = match td
857 .drive_transaction(
858 Some(request.transaction.clone()),
859 SubmitTransactionOptions {
860 forwarded_client_addr: client_addr,
861 ..Default::default()
862 },
863 Some(WAIT_FOR_FINALITY_TIMEOUT),
864 skip_certification,
865 )
866 .await
867 {
868 Ok(response) => response,
869 Err(e) => {
870 warn!(?tx_digest, "TransactionDriver submission failed: {e}");
871 let error = map_td_error_to_qd(e);
872 guard.publish(Err(error.clone()));
873 return Err(error);
874 }
875 };
876
877 debug!(?tx_digest, "TransactionDriver submission succeeded");
878
879 let td_response = Arc::new(td_response);
880 guard.publish(Ok(td_response.clone()));
881 drop(guard);
886 let td_response = Arc::try_unwrap(td_response).unwrap_or_else(|shared| (*shared).clone());
887
888 Ok(Self::response_from_driver_response(td_response, &request))
889 }
890
891 fn response_from_driver_response(
894 td_response: QuorumTransactionResponse,
895 request: &ExecuteTransactionRequestV1,
896 ) -> ExecuteTransactionResponseV1 {
897 let QuorumTransactionResponse {
898 effects,
899 events,
900 input_objects,
901 output_objects,
902 auxiliary_data,
903 } = td_response;
904 ExecuteTransactionResponseV1 {
905 effects: convert_td_to_qd_effects(effects),
906 events: request.include_events.then_some(events).flatten(),
907 input_objects: request
908 .include_input_objects
909 .then_some(input_objects)
910 .flatten(),
911 output_objects: request
912 .include_output_objects
913 .then_some(output_objects)
914 .flatten(),
915 auxiliary_data: request
916 .include_auxiliary_data
917 .then_some(auxiliary_data)
918 .flatten(),
919 }
920 }
921
922 async fn await_in_flight_transaction(
933 mut receiver: watch::Receiver<Option<InFlightSubmissionResult>>,
934 td: &Arc<TransactionDriver<A>>,
935 validator_state: &Arc<AuthorityState>,
936 tx_digest: TransactionDigest,
937 request: &ExecuteTransactionRequestV1,
938 client_addr: Option<SocketAddr>,
939 skip_certification: bool,
940 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
941 let published = tokio::time::timeout(
946 WAIT_FOR_FINALITY_TIMEOUT,
947 receiver.wait_for(|outcome| outcome.is_some()),
948 )
949 .await
950 .map_err(|_elapsed| QuorumDriverError::TimeoutBeforeFinality)?
951 .ok()
952 .and_then(|outcome_ref| outcome_ref.clone());
953
954 let Some(outcome) = published else {
955 return Self::response_from_checkpoint_inclusion(validator_state, tx_digest, request)
961 .await;
962 };
963 let td_response = outcome?;
964
965 let uncertified = matches!(
966 td_response.effects.finality_info,
967 TdEffectsFinalityInfo::UncertifiedSingleValidator(_)
968 );
969 if uncertified && !skip_certification {
970 let certified = tokio::time::timeout(
979 WAIT_FOR_FINALITY_TIMEOUT,
980 td.certify_transaction(
981 tx_digest,
982 SubmitTransactionOptions {
983 forwarded_client_addr: client_addr,
984 ..Default::default()
985 },
986 ),
987 )
988 .await
989 .map_err(|_elapsed| QuorumDriverError::TimeoutBeforeFinality)?
990 .map_err(map_td_error_to_qd)?;
991 return Ok(Self::response_from_driver_response(certified, request));
992 }
993
994 Ok(Self::response_from_driver_response(
995 (*td_response).clone(),
996 request,
997 ))
998 }
999
1000 async fn response_from_checkpoint_inclusion(
1007 validator_state: &Arc<AuthorityState>,
1008 tx_digest: TransactionDigest,
1009 request: &ExecuteTransactionRequestV1,
1010 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
1011 let digests = [tx_digest];
1012 let seq = validator_state
1021 .wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT)
1022 .await
1023 .ok()
1024 .and_then(|inclusion| inclusion.get(&tx_digest).map(|&(seq, _)| seq))
1025 .ok_or(QuorumDriverError::TimeoutBeforeFinality)?;
1026 Self::build_response_from_cache(
1027 validator_state,
1028 tx_digest,
1029 seq,
1030 request.include_events,
1031 request.include_input_objects,
1032 request.include_output_objects,
1033 )
1034 }
1035
1036 #[instrument(level = "trace", skip_all, fields(tx_digest = ?request.transaction.digest()))]
1040 async fn execute_transaction_impl(
1041 &self,
1042 quorum_driver: &Arc<QuorumDriverHandler<A>>,
1043 epoch_store: &Arc<AuthorityPerEpochStore>,
1044 request: ExecuteTransactionRequestV1,
1045 transaction: VerifiedTransaction,
1046 client_addr: Option<SocketAddr>,
1047 ) -> Result<QuorumDriverResponse, QuorumDriverError> {
1048 let (_in_flight_metrics_guards, good_response_metrics) = self.update_metrics(&transaction);
1049 let tx_digest = *transaction.digest();
1050 debug!(?tx_digest, "TO Received transaction execution request.");
1051
1052 let (_e2e_latency_timer, _txn_finality_timer) = if transaction.contains_shared_object() {
1053 (
1054 self.metrics.request_latency_shared_obj.start_timer(),
1055 self.metrics
1056 .wait_for_finality_latency_shared_obj
1057 .start_timer(),
1058 )
1059 } else {
1060 (
1061 self.metrics.request_latency_single_writer.start_timer(),
1062 self.metrics
1063 .wait_for_finality_latency_single_writer
1064 .start_timer(),
1065 )
1066 };
1067
1068 let wait_for_finality_gauge = self.metrics.wait_for_finality_in_flight.clone();
1070 wait_for_finality_gauge.inc();
1071 let _wait_for_finality_gauge = scopeguard::guard(wait_for_finality_gauge, |in_flight| {
1072 in_flight.dec();
1073 });
1074
1075 let ticket = self
1076 .submit(
1077 quorum_driver,
1078 epoch_store.clone(),
1079 transaction.clone(),
1080 request,
1081 client_addr,
1082 )
1083 .await
1084 .map_err(|e| {
1085 warn!(?tx_digest, "QuorumDriverInternalError: {e:?}");
1086 QuorumDriverError::QuorumDriverInternal(e)
1087 })?;
1088
1089 let Ok(result) = timeout(WAIT_FOR_FINALITY_TIMEOUT, ticket).await else {
1090 debug!(?tx_digest, "Timeout waiting for transaction finality.");
1091 self.metrics.wait_for_finality_timeout.inc();
1092 return Err(QuorumDriverError::TimeoutBeforeFinality);
1093 };
1094 add_server_timing("wait_for_finality");
1095
1096 drop(_txn_finality_timer);
1097 drop(_wait_for_finality_gauge);
1098 self.metrics.wait_for_finality_finished.inc();
1099
1100 match result {
1101 Err(err) => {
1102 warn!(?tx_digest, "QuorumDriverInternalError: {err:?}");
1103 Err(QuorumDriverError::QuorumDriverInternal(err))
1104 }
1105 Ok(Err(err)) => Err(err),
1106 Ok(Ok(response)) => {
1107 good_response_metrics.inc();
1108 Ok(response)
1109 }
1110 }
1111 }
1112
1113 #[instrument(name = "tx_orchestrator_submit", level = "trace", skip_all)]
1116 async fn submit(
1117 &self,
1118 quorum_driver: &Arc<QuorumDriverHandler<A>>,
1119 epoch_store: Arc<AuthorityPerEpochStore>,
1120 transaction: VerifiedTransaction,
1121 request: ExecuteTransactionRequestV1,
1122 client_addr: Option<SocketAddr>,
1123 ) -> IotaResult<impl Future<Output = IotaResult<QuorumDriverResult>> + '_> {
1124 let tx_digest = *transaction.digest();
1125 let ticket = self.notifier.register_one(&tx_digest);
1126 if self
1129 .pending_tx_log
1130 .write_pending_transaction_maybe(&transaction)
1131 .await?
1132 {
1133 debug!(?tx_digest, "no pending request in flight, submitting.");
1134 quorum_driver
1135 .submit_transaction_no_ticket(request.clone(), client_addr)
1136 .await?;
1137 }
1138 let cache_reader = self.validator_state.get_transaction_cache_reader().clone();
1144 let qd = quorum_driver.clone();
1145 Ok(async move {
1146 let digests = [tx_digest];
1147 let effects_await =
1148 epoch_store.within_alive_epoch(cache_reader.try_notify_read_executed_effects(
1149 "TransactionOrchestrator::notify_read_submit_with_qd",
1150 &digests,
1151 ));
1152 let res = match select(ticket, effects_await.boxed()).await {
1154 Either::Left((quorum_driver_response, _)) => Ok(quorum_driver_response),
1155 Either::Right((_, unfinished_quorum_driver_task)) => {
1156 debug!(
1157 ?tx_digest,
1158 "Effects are available in DB, use quorum driver to get a certificate"
1159 );
1160 qd.submit_transaction_no_ticket(request, client_addr)
1161 .await?;
1162 Ok(unfinished_quorum_driver_task.await)
1163 }
1164 };
1165 res
1166 })
1167 }
1168
1169 #[instrument(
1170 name = "tx_orchestrator_wait_for_finalized_tx_executed_locally_with_timeout",
1171 level = "debug",
1172 skip_all,
1173 fields(tx_digest = ?transaction.digest()),
1174 err
1175 )]
1176 async fn wait_for_finalized_tx_executed_locally_with_timeout(
1177 validator_state: &Arc<AuthorityState>,
1178 transaction: &VerifiedTransaction,
1179 metrics: &TransactionOrchestratorMetrics,
1180 ) -> IotaResult {
1181 let tx_digest = *transaction.digest();
1182 metrics.local_execution_in_flight.inc();
1183 let _metrics_guard =
1184 scopeguard::guard(metrics.local_execution_in_flight.clone(), |in_flight| {
1185 in_flight.dec();
1186 });
1187
1188 let _guard = if transaction.contains_shared_object() {
1189 metrics.local_execution_latency_shared_obj.start_timer()
1190 } else {
1191 metrics.local_execution_latency_single_writer.start_timer()
1192 };
1193 debug!(
1194 ?tx_digest,
1195 "Waiting for finalized tx to be executed locally."
1196 );
1197 match timeout(
1198 LOCAL_EXECUTION_TIMEOUT,
1199 validator_state
1200 .get_transaction_cache_reader()
1201 .try_notify_read_executed_effects_digests(
1202 "TransactionOrchestrator::notify_read_wait_for_local_execution",
1203 &[tx_digest],
1204 ),
1205 )
1206 .instrument(trace_span!("local_execution"))
1207 .await
1208 {
1209 Err(_elapsed) => {
1210 debug!(
1211 ?tx_digest,
1212 "Waiting for finalized tx to be executed locally timed out within {:?}.",
1213 LOCAL_EXECUTION_TIMEOUT
1214 );
1215 metrics.local_execution_timeout.inc();
1216 Err(IotaError::Timeout)
1217 }
1218 Ok(Err(err)) => {
1219 debug!(
1220 ?tx_digest,
1221 "Waiting for finalized tx to be executed locally failed with error: {:?}", err
1222 );
1223 metrics.local_execution_failure.inc();
1224 Err(IotaError::TransactionOrchestratorLocalExecution {
1225 error: err.to_string(),
1226 })
1227 }
1228 Ok(Ok(_)) => {
1229 metrics.local_execution_success.inc();
1230 Ok(())
1231 }
1232 }
1233 }
1234
1235 async fn loop_pending_transaction_log(
1237 mut effects_receiver: Receiver<QuorumDriverEffectsQueueResult>,
1238 pending_transaction_log: Arc<WritePathPendingTransactionLog>,
1239 ) {
1240 loop {
1241 match effects_receiver.recv().await {
1242 Ok(Ok((transaction, ..))) => {
1243 let tx_digest = transaction.digest();
1244 if let Err(err) = pending_transaction_log.finish_transaction(tx_digest) {
1245 error!(
1246 ?tx_digest,
1247 "Failed to finish transaction in pending transaction log: {err}"
1248 );
1249 }
1250 }
1251 Ok(Err((tx_digest, _err))) => {
1252 if let Err(err) = pending_transaction_log.finish_transaction(&tx_digest) {
1253 error!(
1254 ?tx_digest,
1255 "Failed to finish transaction in pending transaction log: {err}"
1256 );
1257 }
1258 }
1259 Err(RecvError::Closed) => {
1260 error!("Sender of effects subscriber queue has been dropped!");
1261 return;
1262 }
1263 Err(RecvError::Lagged(skipped_count)) => {
1264 warn!("Skipped {skipped_count} transasctions in effects subscriber queue.");
1265 }
1266 }
1267 }
1268 }
1269
1270 #[cfg(any(test, feature = "test-utils"))]
1274 pub fn quorum_driver(&self) -> Option<&Arc<QuorumDriverHandler<A>>> {
1275 self.quorum_driver.as_ref()
1276 }
1277
1278 #[cfg(any(test, feature = "test-utils"))]
1280 pub fn clone_quorum_driver(&self) -> Option<Arc<QuorumDriverHandler<A>>> {
1281 self.quorum_driver.clone()
1282 }
1283
1284 pub fn clone_authority_aggregator(&self) -> Arc<AuthorityAggregator<A>> {
1287 self.transaction_driver.authority_aggregator().load_full()
1288 }
1289
1290 pub fn subscribe_to_effects_queue(&self) -> Option<Receiver<QuorumDriverEffectsQueueResult>> {
1294 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
1295 if epoch_store.protocol_config().enable_pcool_flow() {
1296 return None;
1297 }
1298 self.quorum_driver
1299 .as_ref()
1300 .map(|quorum_driver| quorum_driver.subscribe_to_effects())
1301 }
1302
1303 #[cfg(any(test, feature = "test-utils"))]
1305 pub fn select_driver_for_testing(
1306 &self,
1307 epoch_store: &AuthorityPerEpochStore,
1308 ) -> Result<(), QuorumDriverError> {
1309 self.select_driver(epoch_store).map(|_| ())
1310 }
1311
1312 fn update_metrics(
1313 &'_ self,
1314 transaction: &VerifiedTransaction,
1315 ) -> (impl Drop, &'_ GenericCounter<AtomicU64>) {
1316 let (in_flight, good_response) = if transaction.contains_shared_object() {
1317 self.metrics.total_req_received_shared_object.inc();
1318 (
1319 self.metrics.req_in_flight_shared_object.clone(),
1320 &self.metrics.good_response_shared_object,
1321 )
1322 } else {
1323 self.metrics.total_req_received_single_writer.inc();
1324 (
1325 self.metrics.req_in_flight_single_writer.clone(),
1326 &self.metrics.good_response_single_writer,
1327 )
1328 };
1329 in_flight.inc();
1330 (
1331 scopeguard::guard(in_flight, |in_flight| {
1332 in_flight.dec();
1333 }),
1334 good_response,
1335 )
1336 }
1337
1338 fn schedule_txes_in_log(
1339 pending_tx_log: Arc<WritePathPendingTransactionLog>,
1340 quorum_driver: Arc<QuorumDriverHandler<A>>,
1341 ) {
1342 if std::env::var("SKIP_LOADING_FROM_PENDING_TX_LOG").is_ok() {
1343 info!("Skipping loading pending transactions from pending_tx_log.");
1344 return;
1345 }
1346 spawn_logged_monitored_task!(async move {
1347 let pending_txes = pending_tx_log
1348 .load_all_pending_transactions()
1349 .expect("failed to load all pending transactions");
1350 info!(
1351 "Recovering {} pending transactions from pending_tx_log.",
1352 pending_txes.len()
1353 );
1354 for (i, tx) in pending_txes.into_iter().enumerate() {
1355 let tx = tx.into_inner();
1358 let tx_digest = *tx.digest();
1359 if let Err(err) = quorum_driver
1362 .submit_transaction_no_ticket(
1363 ExecuteTransactionRequestV1 {
1364 transaction: tx,
1365 include_events: true,
1366 include_input_objects: false,
1367 include_output_objects: false,
1368 include_auxiliary_data: false,
1369 },
1370 None,
1371 )
1372 .await
1373 {
1374 warn!(
1375 ?tx_digest,
1376 "Failed to enqueue transaction from pending_tx_log, err: {err:?}"
1377 );
1378 } else {
1379 debug!(?tx_digest, "Enqueued transaction from pending_tx_log");
1380 if (i + 1) % 1000 == 0 {
1381 info!("Enqueued {} transactions from pending_tx_log.", i + 1);
1382 }
1383 }
1384 }
1385 });
1389 }
1390
1391 pub fn load_all_pending_transactions(&self) -> IotaResult<Vec<VerifiedTransaction>> {
1392 self.pending_tx_log.load_all_pending_transactions()
1393 }
1394
1395 #[cfg(any(test, feature = "test-utils"))]
1398 pub fn in_flight_duplicates_for_testing(&self, tx_digest: &TransactionDigest) -> Option<usize> {
1399 self.in_flight_transactions
1400 .lock()
1401 .get(tx_digest)
1402 .map(|sender| sender.receiver_count())
1403 }
1404}
1405
1406fn quorum_driver_response_to_v1(response: QuorumDriverResponse) -> ExecuteTransactionResponseV1 {
1410 let QuorumDriverResponse {
1411 effects_cert,
1412 events,
1413 input_objects,
1414 output_objects,
1415 auxiliary_data,
1416 } = response;
1417 ExecuteTransactionResponseV1 {
1418 effects: FinalizedEffects::new_from_effects_cert(effects_cert.into()),
1419 events,
1420 input_objects,
1421 output_objects,
1422 auxiliary_data,
1423 }
1424}
1425
1426fn convert_td_to_qd_effects(td: TdFinalizedEffects) -> FinalizedEffects {
1429 let finality_info = match td.finality_info {
1430 TdEffectsFinalityInfo::Certified(sig) => EffectsFinalityInfo::Certified(sig),
1431 TdEffectsFinalityInfo::Checkpointed(epoch, seq) => {
1432 EffectsFinalityInfo::Checkpointed(epoch, seq)
1433 }
1434 TdEffectsFinalityInfo::QuorumExecuted(epoch) => EffectsFinalityInfo::QuorumExecuted(epoch),
1435 TdEffectsFinalityInfo::UncertifiedSingleValidator(epoch) => {
1436 EffectsFinalityInfo::UncertifiedSingleValidator(epoch)
1437 }
1438 };
1439 FinalizedEffects {
1440 effects: td.effects,
1441 finality_info,
1442 }
1443}
1444
1445fn map_td_error_to_qd(e: TransactionDriverError) -> QuorumDriverError {
1453 use TransactionDriverError::*;
1454 match e {
1455 ValidationFailed { error } => {
1456 QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature { error })
1457 }
1458 TimeoutWithLastRetriableError { .. } => QuorumDriverError::TimeoutBeforeFinality,
1459 RejectedByValidators {
1460 submission_non_retriable_errors,
1461 ..
1462 } => {
1463 let representative = submission_non_retriable_errors
1468 .errors
1469 .into_iter()
1470 .next()
1471 .map(|(msg, _, _, _)| msg)
1472 .unwrap_or_else(|| "transaction rejected as invalid during submission".to_string());
1473 QuorumDriverError::InvalidTransaction(IotaError::Unknown(format!(
1474 "Transaction was rejected as invalid by more than 1/3 of validator stake \
1475 during submission (non-retriable): {representative}"
1476 )))
1477 }
1478 Aborted {
1479 submission_retriable_errors,
1480 submission_non_retriable_errors,
1481 ..
1482 } => {
1483 let attempts = count_validator_attempts(&submission_retriable_errors)
1488 + count_validator_attempts(&submission_non_retriable_errors);
1489 QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
1490 total_attempts: attempts,
1491 }
1492 }
1493 other @ ForkedExecution { .. } => {
1494 let msg = other.to_string();
1498 error!("TransactionDriver observed forked execution: {msg}");
1499 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1500 }
1501 other @ ClientInternal { .. } => {
1502 let msg = other.to_string();
1503 warn!("TransactionDriver client-internal error: {msg}");
1504 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1505 }
1506 other @ SubmittedButFetchFailed { .. } => {
1507 let msg = other.to_string();
1508 warn!("TransactionDriver submitted transaction but failed to fetch effects: {msg}");
1509 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1510 }
1511 }
1512}
1513
1514fn count_validator_attempts(errors: &AggregatedRequestErrors) -> u32 {
1515 errors
1516 .errors
1517 .iter()
1518 .map(|(_, authorities, _, _)| authorities.len() as u32)
1519 .sum()
1520}
1521
1522async fn join_submission_task<T>(
1525 handle: tokio::task::JoinHandle<Result<T, QuorumDriverError>>,
1526) -> Result<T, QuorumDriverError> {
1527 handle.await.unwrap_or_else(|e| {
1528 Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
1529 format!("transaction submission task panicked: {e}"),
1530 )))
1531 })
1532}
1533
1534#[derive(Clone)]
1536pub struct TransactionOrchestratorMetrics {
1537 total_req_received_single_writer: GenericCounter<AtomicU64>,
1538 total_req_received_shared_object: GenericCounter<AtomicU64>,
1539
1540 good_response_single_writer: GenericCounter<AtomicU64>,
1541 good_response_shared_object: GenericCounter<AtomicU64>,
1542
1543 req_in_flight_single_writer: GenericGauge<AtomicI64>,
1544 req_in_flight_shared_object: GenericGauge<AtomicI64>,
1545
1546 wait_for_finality_in_flight: GenericGauge<AtomicI64>,
1547 wait_for_finality_finished: GenericCounter<AtomicU64>,
1548 wait_for_finality_timeout: GenericCounter<AtomicU64>,
1549
1550 local_execution_in_flight: GenericGauge<AtomicI64>,
1551 local_execution_success: GenericCounter<AtomicU64>,
1552 local_execution_timeout: GenericCounter<AtomicU64>,
1553 local_execution_failure: GenericCounter<AtomicU64>,
1554
1555 early_cached_response: GenericCounter<AtomicU64>,
1556
1557 skip_effect_cert_events_cache_miss: GenericCounter<AtomicU64>,
1562
1563 skip_effect_cert_checkpoint_overrode_driver: GenericCounter<AtomicU64>,
1569
1570 request_latency_single_writer: Histogram,
1571 request_latency_shared_obj: Histogram,
1572 wait_for_finality_latency_single_writer: Histogram,
1573 wait_for_finality_latency_shared_obj: Histogram,
1574 local_execution_latency_single_writer: Histogram,
1575 local_execution_latency_shared_obj: Histogram,
1576}
1577
1578impl TransactionOrchestratorMetrics {
1582 pub fn new(registry: &Registry) -> Self {
1583 let total_req_received = register_int_counter_vec_with_registry!(
1584 "tx_orchestrator_total_req_received",
1585 "Total number of executions request Transaction Orchestrator receives, group by tx type",
1586 &["tx_type"],
1587 registry;
1588 MetricLevel::Warn,
1589 )
1590 .unwrap();
1591
1592 let total_req_received_single_writer =
1593 total_req_received.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1594 let total_req_received_shared_object =
1595 total_req_received.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1596
1597 let good_response = register_int_counter_vec_with_registry!(
1598 "tx_orchestrator_good_response",
1599 "Total number of good responses Transaction Orchestrator generates, group by tx type",
1600 &["tx_type"],
1601 registry;
1602 MetricLevel::Warn,
1603 )
1604 .unwrap();
1605
1606 let good_response_single_writer =
1607 good_response.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1608 let good_response_shared_object = good_response.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1609
1610 let req_in_flight = register_int_gauge_vec_with_registry!(
1611 "tx_orchestrator_req_in_flight",
1612 "Number of requests in flights Transaction Orchestrator processes, group by tx type",
1613 &["tx_type"],
1614 registry;
1615 MetricLevel::Warn,
1616 )
1617 .unwrap();
1618
1619 let req_in_flight_single_writer =
1620 req_in_flight.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1621 let req_in_flight_shared_object = req_in_flight.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1622
1623 let request_latency = register_histogram_vec_with_registry!(
1624 "tx_orchestrator_request_latency",
1625 "Time spent in processing one Transaction Orchestrator request",
1626 &["tx_type"],
1627 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1628 registry;
1629 MetricLevel::Warn,
1630 )
1631 .unwrap();
1632 let wait_for_finality_latency = register_histogram_vec_with_registry!(
1633 "tx_orchestrator_wait_for_finality_latency",
1634 "Time spent in waiting for one Transaction Orchestrator request gets finalized",
1635 &["tx_type"],
1636 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1637 registry;
1638 MetricLevel::Warn,
1639 )
1640 .unwrap();
1641 let local_execution_latency = register_histogram_vec_with_registry!(
1642 "tx_orchestrator_local_execution_latency",
1643 "Time spent in waiting for one Transaction Orchestrator gets locally executed",
1644 &["tx_type"],
1645 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1646 registry;
1647 MetricLevel::Warn,
1648 )
1649 .unwrap();
1650
1651 Self {
1652 total_req_received_single_writer,
1653 total_req_received_shared_object,
1654 good_response_single_writer,
1655 good_response_shared_object,
1656 req_in_flight_single_writer,
1657 req_in_flight_shared_object,
1658 wait_for_finality_in_flight: register_int_gauge_with_registry!(
1659 "tx_orchestrator_wait_for_finality_in_flight",
1660 "Number of in flight txns Transaction Orchestrator are waiting for finality for",
1661 registry;
1662 MetricLevel::Warn,
1663 )
1664 .unwrap(),
1665 wait_for_finality_finished: register_int_counter_with_registry!(
1666 "tx_orchestrator_wait_for_finality_finished",
1667 "Total number of txns Transaction Orchestrator gets responses from Quorum Driver before timeout, either success or failure",
1668 registry;
1669 MetricLevel::Warn,
1670 )
1671 .unwrap(),
1672 wait_for_finality_timeout: register_int_counter_with_registry!(
1673 "tx_orchestrator_wait_for_finality_timeout",
1674 "Total number of txns timing out in waiting for finality Transaction Orchestrator handles",
1675 registry;
1676 MetricLevel::Warn,
1677 )
1678 .unwrap(),
1679 local_execution_in_flight: register_int_gauge_with_registry!(
1680 "tx_orchestrator_local_execution_in_flight",
1681 "Number of local execution txns in flights Transaction Orchestrator handles",
1682 registry;
1683 MetricLevel::Warn,
1684 )
1685 .unwrap(),
1686 local_execution_success: register_int_counter_with_registry!(
1687 "tx_orchestrator_local_execution_success",
1688 "Total number of successful local execution txns Transaction Orchestrator handles",
1689 registry;
1690 MetricLevel::Warn,
1691 )
1692 .unwrap(),
1693 local_execution_timeout: register_int_counter_with_registry!(
1694 "tx_orchestrator_local_execution_timeout",
1695 "Total number of timed-out local execution txns Transaction Orchestrator handles",
1696 registry;
1697 MetricLevel::Warn,
1698 )
1699 .unwrap(),
1700 local_execution_failure: register_int_counter_with_registry!(
1701 "tx_orchestrator_local_execution_failure",
1702 "Total number of failed local execution txns Transaction Orchestrator handles",
1703 registry;
1704 MetricLevel::Warn,
1705 )
1706 .unwrap(),
1707 early_cached_response: register_int_counter_with_registry!(
1708 "tx_orchestrator_early_cached_response",
1709 "Total number of requests returning cached results for already-executed transactions",
1710 registry,
1711 )
1712 .unwrap(),
1713 skip_effect_cert_events_cache_miss: register_int_counter_with_registry!(
1714 "tx_orchestrator_skip_effect_cert_events_cache_miss",
1715 "Number of skip-effect-certification responses rejected because the \
1716 single submitter claimed to have events but the local cache did not \
1717 corroborate them",
1718 registry,
1719 )
1720 .unwrap(),
1721 skip_effect_cert_checkpoint_overrode_driver: register_int_counter_with_registry!(
1722 "tx_orchestrator_skip_effect_cert_checkpoint_overrode_driver",
1723 "Number of skip-effect-certification requests where local checkpoint \
1724 inclusion completed before the TransactionDriver call returned; the \
1725 driver future was cancelled and the response was rebuilt from cache",
1726 registry,
1727 )
1728 .unwrap(),
1729 request_latency_single_writer: request_latency
1730 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1731 request_latency_shared_obj: request_latency.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1732 wait_for_finality_latency_single_writer: wait_for_finality_latency
1733 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1734 wait_for_finality_latency_shared_obj: wait_for_finality_latency
1735 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1736 local_execution_latency_single_writer: local_execution_latency
1737 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1738 local_execution_latency_shared_obj: local_execution_latency
1739 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1740 }
1741 }
1742
1743 pub fn new_for_tests() -> Self {
1744 let registry = Registry::new();
1745 Self::new(®istry)
1746 }
1747}
1748
1749#[async_trait::async_trait]
1750impl<A> iota_types::transaction_executor::TransactionExecutor for TransactionOrchestrator<A>
1751where
1752 A: AuthorityAPI + Send + Sync + 'static + Clone,
1753{
1754 async fn execute_transaction(
1755 &self,
1756 request: ExecuteTransactionRequestV1,
1757 skip_certification: bool,
1758 client_addr: Option<std::net::SocketAddr>,
1759 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
1760 self.execute_transaction_v1(request, skip_certification, client_addr)
1761 .await
1762 }
1763
1764 fn simulate_transaction(
1765 &self,
1766 transaction: Transaction,
1767 checks: VmChecks,
1768 ) -> Result<SimulateTransactionResult, IotaError> {
1769 self.validator_state
1770 .simulate_transaction(transaction, checks)
1771 }
1772
1773 async fn wait_for_checkpoint_inclusion(
1780 &self,
1781 digests: &[TransactionDigest],
1782 timeout: Duration,
1783 ) -> Result<BTreeMap<TransactionDigest, (CheckpointSequenceNumber, u64)>, IotaError> {
1784 self.validator_state
1785 .wait_for_checkpoint_inclusion(digests, timeout)
1786 .await
1787 }
1788
1789 fn read_transaction_from_cache(
1790 &self,
1791 digest: &TransactionDigest,
1792 include_events: bool,
1793 include_input_objects: bool,
1794 include_output_objects: bool,
1795 ) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1796 read_cached_transaction_data(
1797 &self.validator_state,
1798 digest,
1799 include_events,
1800 include_input_objects,
1801 include_output_objects,
1802 )
1803 }
1804}
1805
1806fn read_cached_transaction_data(
1811 validator_state: &Arc<AuthorityState>,
1812 digest: &TransactionDigest,
1813 include_events: bool,
1814 include_input_objects: bool,
1815 include_output_objects: bool,
1816) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1817 let cache = validator_state.get_transaction_cache_reader();
1818 let Some(effects) = cache.try_get_executed_effects(digest)? else {
1819 return Ok(None);
1820 };
1821
1822 let events = if include_events && effects.events_digest().is_some() {
1823 Some(validator_state.get_transaction_events(digest)?)
1824 } else {
1825 None
1826 };
1827
1828 let input_objects = if include_input_objects {
1829 Some(
1830 validator_state
1831 .get_transaction_input_objects(&effects)
1832 .map_err(|e| IotaError::Unknown(format!("input objects: {e:?}")))?,
1833 )
1834 } else {
1835 None
1836 };
1837 let output_objects = if include_output_objects {
1838 Some(
1839 validator_state
1840 .get_transaction_output_objects(&effects)
1841 .map_err(|e| IotaError::Unknown(format!("output objects: {e:?}")))?,
1842 )
1843 } else {
1844 None
1845 };
1846
1847 Ok(Some(
1848 iota_types::transaction_executor::CachedTransactionData {
1849 effects,
1850 events,
1851 input_objects,
1852 output_objects,
1853 },
1854 ))
1855}
1856
1857type InFlightSubmissionResult = Result<Arc<QuorumTransactionResponse>, QuorumDriverError>;
1864
1865type InFlightTransactions =
1869 Arc<Mutex<HashMap<TransactionDigest, watch::Sender<Option<InFlightSubmissionResult>>>>>;
1870
1871enum TransactionSubmission {
1876 Driving(TransactionSubmissionGuard),
1877 AlreadyInFlight(watch::Receiver<Option<InFlightSubmissionResult>>),
1878}
1879
1880struct TransactionSubmissionGuard {
1891 in_flight_transactions: InFlightTransactions,
1892 tx_digest: TransactionDigest,
1893}
1894
1895impl TransactionSubmissionGuard {
1896 fn acquire(
1897 in_flight_transactions: InFlightTransactions,
1898 tx_digest: TransactionDigest,
1899 ) -> TransactionSubmission {
1900 {
1901 let mut in_flight = in_flight_transactions.lock();
1902 match in_flight.entry(tx_digest) {
1903 Entry::Occupied(entry) => {
1904 return TransactionSubmission::AlreadyInFlight(entry.get().subscribe());
1905 }
1906 Entry::Vacant(entry) => {
1907 let (sender, _initial_receiver) = watch::channel(None);
1908 entry.insert(sender);
1909 debug!(?tx_digest, "added transaction to in-flight map");
1910 }
1911 }
1912 }
1913 TransactionSubmission::Driving(Self {
1914 in_flight_transactions,
1915 tx_digest,
1916 })
1917 }
1918
1919 fn publish(&self, result: InFlightSubmissionResult) {
1924 if let Some(sender) = self.in_flight_transactions.lock().get(&self.tx_digest) {
1925 sender.send_replace(Some(result));
1926 }
1927 }
1928}
1929
1930impl Drop for TransactionSubmissionGuard {
1931 fn drop(&mut self) {
1932 self.in_flight_transactions.lock().remove(&self.tx_digest);
1933 }
1934}
1935
1936#[cfg(test)]
1937mod tests {
1938 use super::*;
1939
1940 fn acquire_driving(
1941 in_flight: &InFlightTransactions,
1942 tx_digest: TransactionDigest,
1943 ) -> TransactionSubmissionGuard {
1944 match TransactionSubmissionGuard::acquire(in_flight.clone(), tx_digest) {
1945 TransactionSubmission::Driving(guard) => guard,
1946 TransactionSubmission::AlreadyInFlight(_) => {
1947 panic!("expected to acquire the driving submission")
1948 }
1949 }
1950 }
1951
1952 fn acquire_duplicate(
1953 in_flight: &InFlightTransactions,
1954 tx_digest: TransactionDigest,
1955 ) -> watch::Receiver<Option<InFlightSubmissionResult>> {
1956 match TransactionSubmissionGuard::acquire(in_flight.clone(), tx_digest) {
1957 TransactionSubmission::Driving(_) => {
1958 panic!("expected the digest to already be in flight")
1959 }
1960 TransactionSubmission::AlreadyInFlight(receiver) => receiver,
1961 }
1962 }
1963
1964 #[tokio::test]
1965 async fn duplicate_submission_receives_published_outcome() {
1966 let in_flight = InFlightTransactions::default();
1967 let tx_digest = TransactionDigest::random();
1968
1969 let guard = acquire_driving(&in_flight, tx_digest);
1970 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
1971
1972 guard.publish(Err(QuorumDriverError::TimeoutBeforeFinality));
1973 drop(guard);
1974
1975 let outcome = receiver
1978 .wait_for(|outcome| outcome.is_some())
1979 .await
1980 .expect("outcome was published before the sender dropped")
1981 .clone()
1982 .expect("wait_for only returns once the outcome is Some");
1983 assert!(matches!(
1984 outcome,
1985 Err(QuorumDriverError::TimeoutBeforeFinality)
1986 ));
1987 assert!(
1988 in_flight.lock().is_empty(),
1989 "guard drop must remove the in-flight entry"
1990 );
1991 }
1992
1993 #[tokio::test]
1994 async fn duplicate_subscribing_after_publish_receives_outcome() {
1995 let in_flight = InFlightTransactions::default();
1996 let tx_digest = TransactionDigest::random();
1997
1998 let guard = acquire_driving(&in_flight, tx_digest);
1999 guard.publish(Err(QuorumDriverError::TimeoutBeforeFinality));
2000
2001 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
2005 drop(guard);
2006
2007 let outcome = receiver
2008 .wait_for(|outcome| outcome.is_some())
2009 .await
2010 .expect("the outcome is stored in the channel regardless of subscribers")
2011 .clone()
2012 .expect("wait_for only returns once the outcome is Some");
2013 assert!(matches!(
2014 outcome,
2015 Err(QuorumDriverError::TimeoutBeforeFinality)
2016 ));
2017 }
2018
2019 #[tokio::test]
2020 async fn dropped_guard_without_outcome_closes_channel() {
2021 let in_flight = InFlightTransactions::default();
2022 let tx_digest = TransactionDigest::random();
2023
2024 let guard = acquire_driving(&in_flight, tx_digest);
2025 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
2026 drop(guard);
2027
2028 receiver
2029 .wait_for(|outcome| outcome.is_some())
2030 .await
2031 .expect_err("dropping the guard without publishing must close the channel");
2032 assert!(in_flight.lock().is_empty());
2033
2034 let _guard = acquire_driving(&in_flight, tx_digest);
2036 }
2037
2038 async fn build_orchestrator_with_pcool(
2039 enable_pcool: bool,
2040 ) -> (
2041 Arc<AuthorityState>,
2042 TransactionOrchestrator<NetworkAuthorityClient>,
2043 tempfile::TempDir,
2044 tokio::sync::broadcast::Sender<IotaSystemState>,
2045 ) {
2046 use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
2047
2048 use crate::{
2049 authority::test_authority_builder::TestAuthorityBuilder,
2050 authority_aggregator::AuthorityAggregatorBuilder,
2051 };
2052
2053 telemetry_subscribers::init_for_testing();
2054 let network_config =
2055 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
2056
2057 let mut protocol_config =
2058 ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown);
2059 protocol_config.set_enable_pcool_flow_for_testing(enable_pcool);
2060 let state = TestAuthorityBuilder::new()
2061 .with_network_config(&network_config, 0)
2062 .with_protocol_config(protocol_config)
2063 .build()
2064 .await;
2065
2066 let (aggregator, _clients) =
2067 AuthorityAggregatorBuilder::from_genesis(&network_config.genesis)
2068 .build_network_clients();
2069 let (reconfig_tx, reconfig_rx) = tokio::sync::broadcast::channel(16);
2070 let tempdir = tempfile::tempdir().unwrap();
2071 let orchestrator = TransactionOrchestrator::new_with_auth_aggregator(
2072 Arc::new(aggregator),
2073 state.clone(),
2074 reconfig_rx,
2075 tempdir.path(),
2076 &Registry::new(),
2077 None,
2078 );
2079 (state, orchestrator, tempdir, reconfig_tx)
2080 }
2081
2082 #[tokio::test(flavor = "multi_thread")]
2085 async fn qd_recovery_eager_on_flag_off_boot() {
2086 let (state, orchestrator, _tempdir, _reconfig_tx) =
2087 build_orchestrator_with_pcool(false).await;
2088 assert!(orchestrator.quorum_driver().is_some());
2089 assert!(
2090 orchestrator
2091 .select_driver_for_testing(&state.epoch_store_for_testing())
2092 .is_ok()
2093 );
2094 }
2095
2096 #[tokio::test(flavor = "multi_thread")]
2099 async fn flag_on_boot_rejects_selection_after_rollback() {
2100 use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
2101
2102 let (state, orchestrator, _tempdir, _reconfig_tx) =
2103 build_orchestrator_with_pcool(true).await;
2104 assert!(orchestrator.quorum_driver().is_none());
2105
2106 assert!(
2108 orchestrator
2109 .select_driver_for_testing(&state.epoch_store_for_testing())
2110 .is_ok()
2111 );
2112
2113 let mut protocol_config =
2116 ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown);
2117 protocol_config.set_enable_pcool_flow_for_testing(false);
2118 state
2119 .reconfigure_for_testing_with_protocol_config(protocol_config)
2120 .await;
2121 let epoch_store = state.epoch_store_for_testing();
2122 assert_eq!(epoch_store.epoch(), 1);
2123
2124 assert!(matches!(
2125 orchestrator.select_driver_for_testing(&epoch_store),
2126 Err(QuorumDriverError::QuorumDriverInternal(_))
2127 ));
2128 assert!(orchestrator.subscribe_to_effects_queue().is_none());
2129 }
2130}