1use std::{
10 collections::{BTreeMap, HashMap, hash_map::Entry},
11 net::SocketAddr,
12 ops::Deref,
13 path::Path,
14 sync::Arc,
15 time::Duration,
16};
17
18use futures::{
19 FutureExt,
20 future::{Either, Future, select},
21};
22use iota_common::{debug_fatal, sync::notify_read::NotifyRead};
23use iota_config::NodeConfig;
24use iota_metrics::{
25 TX_TYPE_SHARED_OBJ_TX, TX_TYPE_SINGLE_WRITER_TX, add_server_timing,
26 spawn_logged_monitored_task, spawn_monitored_task,
27};
28use iota_sdk_types::{Transaction, TransactionDigest};
29use iota_storage::write_path_pending_tx_log::WritePathPendingTransactionLog;
30use iota_types::{
31 effects::TransactionEffectsAPI,
32 error::{IotaError, IotaResult},
33 iota_system_state::IotaSystemState,
34 messages_checkpoint::CheckpointSequenceNumber,
35 quorum_driver_types::{
36 EffectsFinalityInfo, ExecuteTransactionRequestType, ExecuteTransactionRequestV1,
37 ExecuteTransactionResponseV1, FinalizedEffects, IsTransactionExecutedLocally,
38 QuorumDriverEffectsQueueResult, QuorumDriverError, QuorumDriverResponse,
39 QuorumDriverResult,
40 },
41 transaction::{SenderSignedTransactionAPI, VerifiedTransaction},
42 transaction_driver_types::{
43 EffectsFinalityInfo as TdEffectsFinalityInfo, FinalizedEffects as TdFinalizedEffects,
44 },
45 transaction_executor::{SimulateTransactionResult, VmChecks},
46};
47use parking_lot::Mutex;
48use prometheus_filtered::{
49 Histogram, MetricLevel, Registry,
50 core::{AtomicI64, AtomicU64, GenericCounter, GenericGauge},
51 register_histogram_vec_with_registry, register_int_counter_vec_with_registry,
52 register_int_counter_with_registry, register_int_gauge_vec_with_registry,
53 register_int_gauge_with_registry,
54};
55use tokio::{
56 sync::{
57 broadcast::{Receiver, error::RecvError},
58 watch,
59 },
60 task::JoinHandle,
61 time::timeout,
62};
63use tracing::{Instrument, debug, error, info, instrument, trace_span, warn};
64
65use crate::{
66 authority::{AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore},
67 authority_aggregator::AuthorityAggregator,
68 authority_client::{AuthorityAPI, NetworkAuthorityClient},
69 quorum_driver::{
70 QuorumDriverHandler, QuorumDriverHandlerBuilder, QuorumDriverMetrics,
71 reconfig_observer::{OnsiteReconfigObserver, ReconfigObserver},
72 },
73 transaction_driver::{
74 AggregatedRequestErrors, QuorumTransactionResponse, SubmitTransactionOptions,
75 TransactionDriver, TransactionDriverError, TransactionDriverMetrics,
76 reconfig_observer::OnsiteReconfigObserver as TdOnsiteReconfigObserver,
77 },
78 validator_client_monitor::ValidatorClientMetrics,
79};
80
81const LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(10);
84
85const WAIT_FOR_FINALITY_TIMEOUT: Duration = Duration::from_secs(30);
86
87enum Driver<A: Clone> {
91 Quorum(Arc<QuorumDriverHandler<A>>),
93 Transaction(Arc<TransactionDriver<A>>),
95}
96
97pub struct TransactionOrchestrator<A: Clone> {
102 driver: Driver<A>,
103 validator_state: Arc<AuthorityState>,
104 _local_executor_handle: Option<JoinHandle<()>>,
105 pending_tx_log: Arc<WritePathPendingTransactionLog>,
106 in_flight_transactions: InFlightTransactions,
114 notifier: Arc<NotifyRead<TransactionDigest, QuorumDriverResult>>,
115 metrics: Arc<TransactionOrchestratorMetrics>,
116}
117
118impl TransactionOrchestrator<NetworkAuthorityClient> {
119 pub fn new_with_auth_aggregator(
120 validators: Arc<AuthorityAggregator<NetworkAuthorityClient>>,
121 validator_state: Arc<AuthorityState>,
122 reconfig_channel: Receiver<IotaSystemState>,
123 parent_path: &Path,
124 prometheus_registry: &Registry,
125 node_config: Option<&NodeConfig>,
126 ) -> Self {
127 let epoch_store = validator_state.load_epoch_store_one_call_per_task();
129 let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
130
131 let td_reconfig_observer = if use_transaction_driver {
133 Some(TdOnsiteReconfigObserver::new(
134 reconfig_channel.resubscribe(),
135 validator_state.get_object_cache_reader().clone(),
136 validator_state.clone_committee_store(),
137 validators.safe_client_metrics_base.clone(),
138 ))
139 } else {
140 None
141 };
142
143 let qd_reconfig_observer = if !use_transaction_driver {
145 Some(OnsiteReconfigObserver::new(
146 reconfig_channel.resubscribe(),
147 validator_state.get_object_cache_reader().clone(),
148 validator_state.clone_committee_store(),
149 validators.safe_client_metrics_base.clone(),
150 validators.metrics.deref().clone(),
151 ))
152 } else {
153 None
154 };
155
156 TransactionOrchestrator::new(
157 validators,
158 validator_state,
159 parent_path,
160 prometheus_registry,
161 qd_reconfig_observer,
162 td_reconfig_observer,
163 node_config,
164 )
165 }
166}
167
168impl<A> TransactionOrchestrator<A>
169where
170 A: AuthorityAPI + Send + Sync + 'static + Clone,
171 OnsiteReconfigObserver: ReconfigObserver<A>,
172 TdOnsiteReconfigObserver: crate::transaction_driver::reconfig_observer::ReconfigObserver<A>,
173{
174 pub fn new(
175 validators: Arc<AuthorityAggregator<A>>,
176 validator_state: Arc<AuthorityState>,
177 parent_path: &Path,
178 prometheus_registry: &Registry,
179 reconfig_observer: Option<OnsiteReconfigObserver>,
180 td_reconfig_observer: Option<TdOnsiteReconfigObserver>,
181 node_config: Option<&NodeConfig>,
182 ) -> Self {
183 let epoch_store = validator_state.load_epoch_store_one_call_per_task();
185 let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
186
187 let notifier = Arc::new(NotifyRead::new());
188 let metrics = Arc::new(TransactionOrchestratorMetrics::new(prometheus_registry));
189 let pending_tx_log = Arc::new(WritePathPendingTransactionLog::new(
190 parent_path.join("fullnode_pending_transactions"),
191 ));
192
193 let (driver, _local_executor_handle) = if !use_transaction_driver {
194 let qd_metrics = Arc::new(QuorumDriverMetrics::new(prometheus_registry));
195 let reconfig_observer = Arc::new(
196 reconfig_observer
197 .expect("QuorumDriver reconfig observer required when P-COOL is disabled"),
198 );
199 let handler = Arc::new(
200 QuorumDriverHandlerBuilder::new(validators, qd_metrics)
201 .with_notifier(notifier.clone())
202 .with_reconfig_observer(reconfig_observer)
203 .start(),
204 );
205 let effects_receiver = handler.subscribe_to_effects();
206 let pending_tx_log_clone = pending_tx_log.clone();
207 let local_executor_handle = spawn_monitored_task!(async move {
208 Self::loop_pending_transaction_log(effects_receiver, pending_tx_log_clone).await;
209 });
210 Self::schedule_txes_in_log(pending_tx_log.clone(), handler.clone());
214 (Driver::Quorum(handler), Some(local_executor_handle))
215 } else {
216 let td_metrics = Arc::new(TransactionDriverMetrics::new(prometheus_registry));
217 let client_metrics = Arc::new(ValidatorClientMetrics::new(prometheus_registry));
218 let observer = td_reconfig_observer
219 .expect("TransactionDriver reconfig observer required when P-COOL is enabled");
220 (
221 Driver::Transaction(TransactionDriver::new(
222 validators,
223 Arc::new(observer),
224 td_metrics,
225 node_config,
226 client_metrics,
227 )),
228 None,
229 )
230 };
231
232 Self {
233 driver,
234 validator_state,
235 _local_executor_handle,
236 pending_tx_log,
237 in_flight_transactions: Default::default(),
238 notifier,
239 metrics,
240 }
241 }
242}
243
244impl<A> TransactionOrchestrator<A>
245where
246 A: AuthorityAPI + Send + Sync + 'static + Clone,
247{
248 #[instrument(name = "tx_orchestrator_execute_transaction_block", level = "trace", skip_all,
249 fields(
250 tx_digest = ?request.transaction.digest(),
251 tx_type = ?request_type,
252 ),
253 err)]
254 pub async fn execute_transaction_block(
255 &self,
256 request: ExecuteTransactionRequestV1,
257 request_type: ExecuteTransactionRequestType,
258 client_addr: Option<SocketAddr>,
259 ) -> Result<(ExecuteTransactionResponseV1, IsTransactionExecutedLocally), QuorumDriverError>
260 {
261 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
262
263 let transaction = epoch_store
264 .verify_transaction(request.transaction.clone())
265 .map_err(QuorumDriverError::InvalidUserSignature)?;
266
267 let include_events = request.include_events;
272 let include_input_objects = request.include_input_objects;
273 let include_output_objects = request.include_output_objects;
274
275 let tx_digest = *transaction.digest();
276
277 if let Some(response) = Self::build_response_from_local_effects(
281 &self.validator_state,
282 &tx_digest,
283 include_events,
284 include_input_objects,
285 include_output_objects,
286 )? {
287 self.metrics.early_cached_response.inc();
288 debug!(
289 ?tx_digest,
290 "Returning cached results for already-executed transaction"
291 );
292 return Ok((response, true));
293 }
294
295 transaction
301 .validity_check(&epoch_store.tx_validity_check_context())
302 .map_err(QuorumDriverError::InvalidTransaction)?;
303
304 let wait_for_local_execution = matches!(
305 request_type,
306 ExecuteTransactionRequestType::WaitForLocalExecution
307 );
308 let (mut response, seq) = match (&self.driver, wait_for_local_execution) {
309 (Driver::Transaction(td), true) => {
310 let td = td.clone();
311 let in_flight_transactions = self.in_flight_transactions.clone();
312 let validator_state = self.validator_state.clone();
313 let metrics = self.metrics.clone();
314 join_submission_task(spawn_monitored_task!(Self::submit_with_checkpoint_race(
318 td,
319 in_flight_transactions,
320 validator_state,
321 metrics,
322 request,
323 client_addr,
324 tx_digest,
325 )))
326 .await?
327 }
328 (Driver::Transaction(td), false) => {
329 let td = td.clone();
330 let in_flight_transactions = self.in_flight_transactions.clone();
331 let validator_state = self.validator_state.clone();
332 let result = join_submission_task(spawn_monitored_task!(
334 Self::submit_with_transaction_driver(
335 td,
336 in_flight_transactions,
337 validator_state,
338 request,
339 client_addr,
340 false,
341 )
342 ))
343 .await?;
344 (Some(result), None)
345 }
346 (Driver::Quorum(qd), _) => {
347 let qd_resp = self
348 .execute_transaction_impl(
349 qd,
350 &epoch_store,
351 request,
352 transaction.clone(),
353 client_addr,
354 )
355 .await?;
356 (Some(quorum_driver_response_to_v1(qd_resp)), None)
357 }
358 };
359
360 let needs_cache_rebuild = matches!(
371 response.as_ref().map(|r| &r.effects.finality_info),
372 None | Some(EffectsFinalityInfo::UncertifiedSingleValidator(_)),
373 );
374
375 let executed_locally = if !wait_for_local_execution {
376 false
377 } else if needs_cache_rebuild {
378 let Some(seq) = seq else {
379 return Err(QuorumDriverError::TimeoutBeforeFinality);
385 };
386 match response.as_mut() {
387 Some(existing) => Self::reconcile_effects_from_cache(
388 &self.validator_state,
389 tx_digest,
390 seq,
391 include_events,
392 include_input_objects,
393 include_output_objects,
394 existing,
395 &self.metrics,
396 )?,
397 None => {
398 response = Some(Self::build_response_from_cache(
399 &self.validator_state,
400 tx_digest,
401 seq,
402 include_events,
403 include_input_objects,
404 include_output_objects,
405 )?);
406 }
407 }
408 true
409 } else {
410 let ok = Self::wait_for_finalized_tx_executed_locally_with_timeout(
415 &self.validator_state,
416 &transaction,
417 &self.metrics,
418 )
419 .await
420 .is_ok();
421 add_server_timing("local_execution");
422 ok
423 };
424
425 let response = response.expect("response must be populated before return");
426
427 if matches!(
436 response.effects.finality_info,
437 EffectsFinalityInfo::UncertifiedSingleValidator(_)
438 ) {
439 debug_fatal!(
440 "Uncertified effects (UncertifiedSingleValidator) about to be returned \
441 to the client for tx {:?}",
442 response.effects.effects.transaction_digest()
443 );
444 return Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
445 "internal error: transaction effects not finalized".to_string(),
446 )));
447 }
448
449 Ok((response, executed_locally))
450 }
451
452 fn reconcile_effects_from_cache(
475 validator_state: &Arc<AuthorityState>,
476 tx_digest: TransactionDigest,
477 checkpoint_seq: CheckpointSequenceNumber,
478 include_events: bool,
479 include_input_objects: bool,
480 include_output_objects: bool,
481 response: &mut ExecuteTransactionResponseV1,
482 metrics: &TransactionOrchestratorMetrics,
483 ) -> Result<(), QuorumDriverError> {
484 let rebuilt = Self::build_response_from_cache(
485 validator_state,
486 tx_digest,
487 checkpoint_seq,
488 include_events,
489 include_input_objects,
490 include_output_objects,
491 )?;
492
493 let td_digest = response.effects.effects.digest();
494 let cache_digest = rebuilt.effects.effects.digest();
495 if td_digest != cache_digest {
496 warn!(
497 ?tx_digest,
498 ?td_digest,
499 ?cache_digest,
500 "reconcile_effects_from_cache: TransactionDriver and local cache disagree \
501 on effects digest — replacing with cache (possible byzantine submitter)"
502 );
503 }
504 if include_events && response.events.is_some() && rebuilt.events.is_none() {
505 warn!(
506 ?tx_digest,
507 "reconcile_effects_from_cache: submitter claimed events but cache has \
508 none — discarding (possible byzantine submitter)"
509 );
510 metrics.skip_effect_cert_events_cache_miss.inc();
511 }
512 *response = rebuilt;
513 Ok(())
514 }
515
516 fn build_response_from_cache(
524 validator_state: &Arc<AuthorityState>,
525 tx_digest: TransactionDigest,
526 checkpoint_seq: CheckpointSequenceNumber,
527 include_events: bool,
528 include_input_objects: bool,
529 include_output_objects: bool,
530 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
531 let cached = read_cached_transaction_data(
532 validator_state,
533 &tx_digest,
534 include_events,
535 include_input_objects,
536 include_output_objects,
537 )
538 .map_err(|e| {
539 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(format!(
540 "failed to read cached tx data for {tx_digest:?}: {e:?}"
541 )))
542 })?
543 .ok_or_else(|| {
544 warn!(
548 ?tx_digest,
549 "effects missing from cache after checkpoint inclusion — surfacing as \
550 TimeoutBeforeFinality"
551 );
552 QuorumDriverError::TimeoutBeforeFinality
553 })?;
554 let iota_types::transaction_executor::CachedTransactionData {
555 effects,
556 events,
557 input_objects,
558 output_objects,
559 } = cached;
560
561 let epoch = effects.epoch();
562 Ok(ExecuteTransactionResponseV1 {
563 effects: FinalizedEffects {
564 effects,
565 finality_info: EffectsFinalityInfo::Checkpointed(epoch, checkpoint_seq),
566 },
567 events,
568 input_objects,
569 output_objects,
570 auxiliary_data: None,
571 })
572 }
573
574 fn build_response_from_local_effects(
581 validator_state: &Arc<AuthorityState>,
582 tx_digest: &TransactionDigest,
583 include_events: bool,
584 include_input_objects: bool,
585 include_output_objects: bool,
586 ) -> Result<Option<ExecuteTransactionResponseV1>, QuorumDriverError> {
587 let Some(cached) = read_cached_transaction_data(
588 validator_state,
589 tx_digest,
590 include_events,
591 include_input_objects,
592 include_output_objects,
593 )
594 .map_err(QuorumDriverError::QuorumDriverInternal)?
595 else {
596 return Ok(None);
597 };
598 let iota_types::transaction_executor::CachedTransactionData {
599 effects,
600 events,
601 input_objects,
602 output_objects,
603 } = cached;
604
605 let epoch = effects.epoch();
606 Ok(Some(ExecuteTransactionResponseV1 {
607 effects: FinalizedEffects {
608 effects,
609 finality_info: EffectsFinalityInfo::QuorumExecuted(epoch),
610 },
611 events,
612 input_objects,
613 output_objects,
614 auxiliary_data: None,
615 }))
616 }
617
618 #[instrument(name = "tx_orchestrator_execute_transaction_v1", level = "trace", skip_all,
621 fields(tx_digest = ?request.transaction.digest()))]
622 pub async fn execute_transaction_v1(
623 &self,
624 request: ExecuteTransactionRequestV1,
625 skip_certification: bool,
626 client_addr: Option<SocketAddr>,
627 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
628 let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
629
630 let transaction = epoch_store
631 .verify_transaction(request.transaction.clone())
632 .map_err(QuorumDriverError::InvalidUserSignature)?;
633 let tx_digest = *transaction.digest();
634
635 if let Some(response) = Self::build_response_from_local_effects(
639 &self.validator_state,
640 &tx_digest,
641 request.include_events,
642 request.include_input_objects,
643 request.include_output_objects,
644 )? {
645 self.metrics.early_cached_response.inc();
646 debug!(
647 ?tx_digest,
648 "Returning cached results for already-executed transaction"
649 );
650 return Ok(response);
651 }
652
653 transaction
659 .validity_check(&epoch_store.tx_validity_check_context())
660 .map_err(QuorumDriverError::InvalidTransaction)?;
661
662 match &self.driver {
663 Driver::Transaction(td) => {
664 let td = td.clone();
665 let in_flight_transactions = self.in_flight_transactions.clone();
666 let validator_state = self.validator_state.clone();
667 join_submission_task(spawn_monitored_task!(Self::submit_with_transaction_driver(
675 td,
676 in_flight_transactions,
677 validator_state,
678 request,
679 client_addr,
680 skip_certification,
681 )))
682 .await
683 }
684 Driver::Quorum(qd) => {
685 let qd_resp = self
686 .execute_transaction_impl(qd, &epoch_store, request, transaction, client_addr)
687 .await?;
688 Ok(quorum_driver_response_to_v1(qd_resp))
689 }
690 }
691 }
692
693 #[instrument(name = "tx_orchestrator_submit_with_checkpoint_race", level = "trace", skip_all,
712 fields(tx_digest = ?tx_digest))]
713 async fn submit_with_checkpoint_race(
714 td: Arc<TransactionDriver<A>>,
715 in_flight_transactions: InFlightTransactions,
716 validator_state: Arc<AuthorityState>,
717 metrics: Arc<TransactionOrchestratorMetrics>,
718 request: ExecuteTransactionRequestV1,
719 client_addr: Option<SocketAddr>,
720 tx_digest: TransactionDigest,
721 ) -> Result<
722 (
723 Option<ExecuteTransactionResponseV1>,
724 Option<CheckpointSequenceNumber>,
725 ),
726 QuorumDriverError,
727 > {
728 let digests = [tx_digest];
729 let checkpoint_inclusion =
730 validator_state.wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT);
731 tokio::pin!(checkpoint_inclusion);
732 let driver = Self::submit_with_transaction_driver(
733 td,
734 in_flight_transactions,
735 validator_state.clone(),
736 request,
737 client_addr,
738 true,
739 );
740
741 let seq_for_tx = |inclusion_map: BTreeMap<_, (CheckpointSequenceNumber, _)>| {
742 inclusion_map.get(&tx_digest).map(|&(seq, _)| seq)
743 };
744
745 let result = tokio::select! {
746 biased;
747 driver_result = driver => {
752 let response = Some(driver_result?);
753 let seq = (&mut checkpoint_inclusion).await.ok().and_then(seq_for_tx);
754 (response, seq)
755 }
756 checkpoint_result = &mut checkpoint_inclusion => {
757 metrics.skip_effect_cert_checkpoint_overrode_driver.inc();
758 let seq = checkpoint_result.ok().and_then(seq_for_tx);
763 (None, seq)
764 }
765 };
766 add_server_timing("local_execution");
767 Ok(result)
768 }
769
770 #[instrument(name = "tx_orchestrator_submit_with_td", level = "trace", skip_all,
784 fields(tx_digest = ?request.transaction.digest()))]
785 async fn submit_with_transaction_driver(
786 td: Arc<TransactionDriver<A>>,
787 in_flight_transactions: InFlightTransactions,
788 validator_state: Arc<AuthorityState>,
789 request: ExecuteTransactionRequestV1,
790 client_addr: Option<SocketAddr>,
791 skip_certification: bool,
792 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
793 let tx_digest = *request.transaction.digest();
794
795 let guard = match TransactionSubmissionGuard::acquire(in_flight_transactions, tx_digest) {
801 TransactionSubmission::Driving(guard) => guard,
802 TransactionSubmission::AlreadyInFlight(receiver) => {
803 debug!(
804 ?tx_digest,
805 "transaction already in flight; awaiting its outcome instead of driving a \
806 duplicate submission"
807 );
808 return Self::await_in_flight_transaction(
809 receiver,
810 &td,
811 &validator_state,
812 tx_digest,
813 &request,
814 client_addr,
815 skip_certification,
816 )
817 .await;
818 }
819 };
820
821 let td_response = match td
826 .drive_transaction(
827 Some(request.transaction.clone()),
828 SubmitTransactionOptions {
829 forwarded_client_addr: client_addr,
830 ..Default::default()
831 },
832 Some(WAIT_FOR_FINALITY_TIMEOUT),
833 skip_certification,
834 )
835 .await
836 {
837 Ok(response) => response,
838 Err(e) => {
839 warn!(?tx_digest, "TransactionDriver submission failed: {e}");
840 let error = map_td_error_to_qd(e);
841 guard.publish(Err(error.clone()));
842 return Err(error);
843 }
844 };
845
846 debug!(?tx_digest, "TransactionDriver submission succeeded");
847
848 let td_response = Arc::new(td_response);
849 guard.publish(Ok(td_response.clone()));
850 drop(guard);
855 let td_response = Arc::try_unwrap(td_response).unwrap_or_else(|shared| (*shared).clone());
856
857 Ok(Self::response_from_driver_response(td_response, &request))
858 }
859
860 fn response_from_driver_response(
863 td_response: QuorumTransactionResponse,
864 request: &ExecuteTransactionRequestV1,
865 ) -> ExecuteTransactionResponseV1 {
866 let QuorumTransactionResponse {
867 effects,
868 events,
869 input_objects,
870 output_objects,
871 auxiliary_data,
872 } = td_response;
873 ExecuteTransactionResponseV1 {
874 effects: convert_td_to_qd_effects(effects),
875 events: request.include_events.then_some(events).flatten(),
876 input_objects: request
877 .include_input_objects
878 .then_some(input_objects)
879 .flatten(),
880 output_objects: request
881 .include_output_objects
882 .then_some(output_objects)
883 .flatten(),
884 auxiliary_data: request
885 .include_auxiliary_data
886 .then_some(auxiliary_data)
887 .flatten(),
888 }
889 }
890
891 async fn await_in_flight_transaction(
902 mut receiver: watch::Receiver<Option<InFlightSubmissionResult>>,
903 td: &Arc<TransactionDriver<A>>,
904 validator_state: &Arc<AuthorityState>,
905 tx_digest: TransactionDigest,
906 request: &ExecuteTransactionRequestV1,
907 client_addr: Option<SocketAddr>,
908 skip_certification: bool,
909 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
910 let published = tokio::time::timeout(
915 WAIT_FOR_FINALITY_TIMEOUT,
916 receiver.wait_for(|outcome| outcome.is_some()),
917 )
918 .await
919 .map_err(|_elapsed| QuorumDriverError::TimeoutBeforeFinality)?
920 .ok()
921 .and_then(|outcome_ref| outcome_ref.clone());
922
923 let Some(outcome) = published else {
924 return Self::response_from_checkpoint_inclusion(validator_state, tx_digest, request)
930 .await;
931 };
932 let td_response = outcome?;
933
934 let uncertified = matches!(
935 td_response.effects.finality_info,
936 TdEffectsFinalityInfo::UncertifiedSingleValidator(_)
937 );
938 if uncertified && !skip_certification {
939 let certified = tokio::time::timeout(
948 WAIT_FOR_FINALITY_TIMEOUT,
949 td.certify_transaction(
950 tx_digest,
951 SubmitTransactionOptions {
952 forwarded_client_addr: client_addr,
953 ..Default::default()
954 },
955 ),
956 )
957 .await
958 .map_err(|_elapsed| QuorumDriverError::TimeoutBeforeFinality)?
959 .map_err(map_td_error_to_qd)?;
960 return Ok(Self::response_from_driver_response(certified, request));
961 }
962
963 Ok(Self::response_from_driver_response(
964 (*td_response).clone(),
965 request,
966 ))
967 }
968
969 async fn response_from_checkpoint_inclusion(
976 validator_state: &Arc<AuthorityState>,
977 tx_digest: TransactionDigest,
978 request: &ExecuteTransactionRequestV1,
979 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
980 let digests = [tx_digest];
981 let seq = validator_state
990 .wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT)
991 .await
992 .ok()
993 .and_then(|inclusion| inclusion.get(&tx_digest).map(|&(seq, _)| seq))
994 .ok_or(QuorumDriverError::TimeoutBeforeFinality)?;
995 Self::build_response_from_cache(
996 validator_state,
997 tx_digest,
998 seq,
999 request.include_events,
1000 request.include_input_objects,
1001 request.include_output_objects,
1002 )
1003 }
1004
1005 #[instrument(level = "trace", skip_all, fields(tx_digest = ?request.transaction.digest()))]
1009 async fn execute_transaction_impl(
1010 &self,
1011 quorum_driver: &Arc<QuorumDriverHandler<A>>,
1012 epoch_store: &Arc<AuthorityPerEpochStore>,
1013 request: ExecuteTransactionRequestV1,
1014 transaction: VerifiedTransaction,
1015 client_addr: Option<SocketAddr>,
1016 ) -> Result<QuorumDriverResponse, QuorumDriverError> {
1017 let (_in_flight_metrics_guards, good_response_metrics) = self.update_metrics(&transaction);
1018 let tx_digest = *transaction.digest();
1019 debug!(?tx_digest, "TO Received transaction execution request.");
1020
1021 let (_e2e_latency_timer, _txn_finality_timer) = if transaction.contains_shared_object() {
1022 (
1023 self.metrics.request_latency_shared_obj.start_timer(),
1024 self.metrics
1025 .wait_for_finality_latency_shared_obj
1026 .start_timer(),
1027 )
1028 } else {
1029 (
1030 self.metrics.request_latency_single_writer.start_timer(),
1031 self.metrics
1032 .wait_for_finality_latency_single_writer
1033 .start_timer(),
1034 )
1035 };
1036
1037 let wait_for_finality_gauge = self.metrics.wait_for_finality_in_flight.clone();
1039 wait_for_finality_gauge.inc();
1040 let _wait_for_finality_gauge = scopeguard::guard(wait_for_finality_gauge, |in_flight| {
1041 in_flight.dec();
1042 });
1043
1044 let ticket = self
1045 .submit(
1046 quorum_driver,
1047 epoch_store.clone(),
1048 transaction.clone(),
1049 request,
1050 client_addr,
1051 )
1052 .await
1053 .map_err(|e| {
1054 warn!(?tx_digest, "QuorumDriverInternalError: {e:?}");
1055 QuorumDriverError::QuorumDriverInternal(e)
1056 })?;
1057
1058 let Ok(result) = timeout(WAIT_FOR_FINALITY_TIMEOUT, ticket).await else {
1059 debug!(?tx_digest, "Timeout waiting for transaction finality.");
1060 self.metrics.wait_for_finality_timeout.inc();
1061 return Err(QuorumDriverError::TimeoutBeforeFinality);
1062 };
1063 add_server_timing("wait_for_finality");
1064
1065 drop(_txn_finality_timer);
1066 drop(_wait_for_finality_gauge);
1067 self.metrics.wait_for_finality_finished.inc();
1068
1069 match result {
1070 Err(err) => {
1071 warn!(?tx_digest, "QuorumDriverInternalError: {err:?}");
1072 Err(QuorumDriverError::QuorumDriverInternal(err))
1073 }
1074 Ok(Err(err)) => Err(err),
1075 Ok(Ok(response)) => {
1076 good_response_metrics.inc();
1077 Ok(response)
1078 }
1079 }
1080 }
1081
1082 #[instrument(name = "tx_orchestrator_submit", level = "trace", skip_all)]
1085 async fn submit(
1086 &self,
1087 quorum_driver: &Arc<QuorumDriverHandler<A>>,
1088 epoch_store: Arc<AuthorityPerEpochStore>,
1089 transaction: VerifiedTransaction,
1090 request: ExecuteTransactionRequestV1,
1091 client_addr: Option<SocketAddr>,
1092 ) -> IotaResult<impl Future<Output = IotaResult<QuorumDriverResult>> + '_> {
1093 let tx_digest = *transaction.digest();
1094 let ticket = self.notifier.register_one(&tx_digest);
1095 if self
1098 .pending_tx_log
1099 .write_pending_transaction_maybe(&transaction)
1100 .await?
1101 {
1102 debug!(?tx_digest, "no pending request in flight, submitting.");
1103 quorum_driver
1104 .submit_transaction_no_ticket(request.clone(), client_addr)
1105 .await?;
1106 }
1107 let cache_reader = self.validator_state.get_transaction_cache_reader().clone();
1113 let qd = quorum_driver.clone();
1114 Ok(async move {
1115 let digests = [tx_digest];
1116 let effects_await =
1117 epoch_store.within_alive_epoch(cache_reader.try_notify_read_executed_effects(
1118 "TransactionOrchestrator::notify_read_submit_with_qd",
1119 &digests,
1120 ));
1121 let res = match select(ticket, effects_await.boxed()).await {
1123 Either::Left((quorum_driver_response, _)) => Ok(quorum_driver_response),
1124 Either::Right((_, unfinished_quorum_driver_task)) => {
1125 debug!(
1126 ?tx_digest,
1127 "Effects are available in DB, use quorum driver to get a certificate"
1128 );
1129 qd.submit_transaction_no_ticket(request, client_addr)
1130 .await?;
1131 Ok(unfinished_quorum_driver_task.await)
1132 }
1133 };
1134 res
1135 })
1136 }
1137
1138 #[instrument(
1139 name = "tx_orchestrator_wait_for_finalized_tx_executed_locally_with_timeout",
1140 level = "debug",
1141 skip_all,
1142 fields(tx_digest = ?transaction.digest()),
1143 err
1144 )]
1145 async fn wait_for_finalized_tx_executed_locally_with_timeout(
1146 validator_state: &Arc<AuthorityState>,
1147 transaction: &VerifiedTransaction,
1148 metrics: &TransactionOrchestratorMetrics,
1149 ) -> IotaResult {
1150 let tx_digest = *transaction.digest();
1151 metrics.local_execution_in_flight.inc();
1152 let _metrics_guard =
1153 scopeguard::guard(metrics.local_execution_in_flight.clone(), |in_flight| {
1154 in_flight.dec();
1155 });
1156
1157 let _guard = if transaction.contains_shared_object() {
1158 metrics.local_execution_latency_shared_obj.start_timer()
1159 } else {
1160 metrics.local_execution_latency_single_writer.start_timer()
1161 };
1162 debug!(
1163 ?tx_digest,
1164 "Waiting for finalized tx to be executed locally."
1165 );
1166 match timeout(
1167 LOCAL_EXECUTION_TIMEOUT,
1168 validator_state
1169 .get_transaction_cache_reader()
1170 .try_notify_read_executed_effects_digests(
1171 "TransactionOrchestrator::notify_read_wait_for_local_execution",
1172 &[tx_digest],
1173 ),
1174 )
1175 .instrument(trace_span!("local_execution"))
1176 .await
1177 {
1178 Err(_elapsed) => {
1179 debug!(
1180 ?tx_digest,
1181 "Waiting for finalized tx to be executed locally timed out within {:?}.",
1182 LOCAL_EXECUTION_TIMEOUT
1183 );
1184 metrics.local_execution_timeout.inc();
1185 Err(IotaError::Timeout)
1186 }
1187 Ok(Err(err)) => {
1188 debug!(
1189 ?tx_digest,
1190 "Waiting for finalized tx to be executed locally failed with error: {:?}", err
1191 );
1192 metrics.local_execution_failure.inc();
1193 Err(IotaError::TransactionOrchestratorLocalExecution {
1194 error: err.to_string(),
1195 })
1196 }
1197 Ok(Ok(_)) => {
1198 metrics.local_execution_success.inc();
1199 Ok(())
1200 }
1201 }
1202 }
1203
1204 async fn loop_pending_transaction_log(
1206 mut effects_receiver: Receiver<QuorumDriverEffectsQueueResult>,
1207 pending_transaction_log: Arc<WritePathPendingTransactionLog>,
1208 ) {
1209 loop {
1210 match effects_receiver.recv().await {
1211 Ok(Ok((transaction, ..))) => {
1212 let tx_digest = transaction.digest();
1213 if let Err(err) = pending_transaction_log.finish_transaction(tx_digest) {
1214 error!(
1215 ?tx_digest,
1216 "Failed to finish transaction in pending transaction log: {err}"
1217 );
1218 }
1219 }
1220 Ok(Err((tx_digest, _err))) => {
1221 if let Err(err) = pending_transaction_log.finish_transaction(&tx_digest) {
1222 error!(
1223 ?tx_digest,
1224 "Failed to finish transaction in pending transaction log: {err}"
1225 );
1226 }
1227 }
1228 Err(RecvError::Closed) => {
1229 error!("Sender of effects subscriber queue has been dropped!");
1230 return;
1231 }
1232 Err(RecvError::Lagged(skipped_count)) => {
1233 warn!("Skipped {skipped_count} transasctions in effects subscriber queue.");
1234 }
1235 }
1236 }
1237 }
1238
1239 pub fn quorum_driver(&self) -> Option<&Arc<QuorumDriverHandler<A>>> {
1241 match &self.driver {
1242 Driver::Quorum(handler) => Some(handler),
1243 Driver::Transaction(_) => None,
1244 }
1245 }
1246
1247 pub fn clone_quorum_driver(&self) -> Option<Arc<QuorumDriverHandler<A>>> {
1249 self.quorum_driver().cloned()
1250 }
1251
1252 pub fn transaction_driver(&self) -> Option<&Arc<TransactionDriver<A>>> {
1255 match &self.driver {
1256 Driver::Quorum(_) => None,
1257 Driver::Transaction(td) => Some(td),
1258 }
1259 }
1260
1261 pub fn clone_authority_aggregator(&self) -> Arc<AuthorityAggregator<A>> {
1263 match &self.driver {
1264 Driver::Quorum(qd) => qd.authority_aggregator().load_full(),
1265 Driver::Transaction(td) => td.authority_aggregator().load_full(),
1266 }
1267 }
1268
1269 pub fn subscribe_to_effects_queue(&self) -> Option<Receiver<QuorumDriverEffectsQueueResult>> {
1271 self.quorum_driver().map(|qd| qd.subscribe_to_effects())
1272 }
1273
1274 fn update_metrics(
1275 &'_ self,
1276 transaction: &VerifiedTransaction,
1277 ) -> (impl Drop, &'_ GenericCounter<AtomicU64>) {
1278 let (in_flight, good_response) = if transaction.contains_shared_object() {
1279 self.metrics.total_req_received_shared_object.inc();
1280 (
1281 self.metrics.req_in_flight_shared_object.clone(),
1282 &self.metrics.good_response_shared_object,
1283 )
1284 } else {
1285 self.metrics.total_req_received_single_writer.inc();
1286 (
1287 self.metrics.req_in_flight_single_writer.clone(),
1288 &self.metrics.good_response_single_writer,
1289 )
1290 };
1291 in_flight.inc();
1292 (
1293 scopeguard::guard(in_flight, |in_flight| {
1294 in_flight.dec();
1295 }),
1296 good_response,
1297 )
1298 }
1299
1300 fn schedule_txes_in_log(
1301 pending_tx_log: Arc<WritePathPendingTransactionLog>,
1302 quorum_driver: Arc<QuorumDriverHandler<A>>,
1303 ) {
1304 spawn_logged_monitored_task!(async move {
1305 if std::env::var("SKIP_LOADING_FROM_PENDING_TX_LOG").is_ok() {
1306 info!("Skipping loading pending transactions from pending_tx_log.");
1307 return;
1308 }
1309 let pending_txes = pending_tx_log
1310 .load_all_pending_transactions()
1311 .expect("failed to load all pending transactions");
1312 info!(
1313 "Recovering {} pending transactions from pending_tx_log.",
1314 pending_txes.len()
1315 );
1316 for (i, tx) in pending_txes.into_iter().enumerate() {
1317 let tx = tx.into_inner();
1320 let tx_digest = *tx.digest();
1321 if let Err(err) = quorum_driver
1324 .submit_transaction_no_ticket(
1325 ExecuteTransactionRequestV1 {
1326 transaction: tx,
1327 include_events: true,
1328 include_input_objects: false,
1329 include_output_objects: false,
1330 include_auxiliary_data: false,
1331 },
1332 None,
1333 )
1334 .await
1335 {
1336 warn!(
1337 ?tx_digest,
1338 "Failed to enqueue transaction from pending_tx_log, err: {err:?}"
1339 );
1340 } else {
1341 debug!(?tx_digest, "Enqueued transaction from pending_tx_log");
1342 if (i + 1) % 1000 == 0 {
1343 info!("Enqueued {} transactions from pending_tx_log.", i + 1);
1344 }
1345 }
1346 }
1347 });
1351 }
1352
1353 pub fn load_all_pending_transactions(&self) -> IotaResult<Vec<VerifiedTransaction>> {
1354 self.pending_tx_log.load_all_pending_transactions()
1355 }
1356
1357 #[cfg(any(test, feature = "test-utils"))]
1360 pub fn in_flight_duplicates_for_testing(&self, tx_digest: &TransactionDigest) -> Option<usize> {
1361 self.in_flight_transactions
1362 .lock()
1363 .get(tx_digest)
1364 .map(|sender| sender.receiver_count())
1365 }
1366}
1367
1368fn quorum_driver_response_to_v1(response: QuorumDriverResponse) -> ExecuteTransactionResponseV1 {
1372 let QuorumDriverResponse {
1373 effects_cert,
1374 events,
1375 input_objects,
1376 output_objects,
1377 auxiliary_data,
1378 } = response;
1379 ExecuteTransactionResponseV1 {
1380 effects: FinalizedEffects::new_from_effects_cert(effects_cert.into()),
1381 events,
1382 input_objects,
1383 output_objects,
1384 auxiliary_data,
1385 }
1386}
1387
1388fn convert_td_to_qd_effects(td: TdFinalizedEffects) -> FinalizedEffects {
1391 let finality_info = match td.finality_info {
1392 TdEffectsFinalityInfo::Certified(sig) => EffectsFinalityInfo::Certified(sig),
1393 TdEffectsFinalityInfo::Checkpointed(epoch, seq) => {
1394 EffectsFinalityInfo::Checkpointed(epoch, seq)
1395 }
1396 TdEffectsFinalityInfo::QuorumExecuted(epoch) => EffectsFinalityInfo::QuorumExecuted(epoch),
1397 TdEffectsFinalityInfo::UncertifiedSingleValidator(epoch) => {
1398 EffectsFinalityInfo::UncertifiedSingleValidator(epoch)
1399 }
1400 };
1401 FinalizedEffects {
1402 effects: td.effects,
1403 finality_info,
1404 }
1405}
1406
1407fn map_td_error_to_qd(e: TransactionDriverError) -> QuorumDriverError {
1415 use TransactionDriverError::*;
1416 match e {
1417 ValidationFailed { error } => {
1418 QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature { error })
1419 }
1420 TimeoutWithLastRetriableError { .. } => QuorumDriverError::TimeoutBeforeFinality,
1421 RejectedByValidators {
1422 submission_non_retriable_errors,
1423 ..
1424 } => {
1425 let representative = submission_non_retriable_errors
1430 .errors
1431 .into_iter()
1432 .next()
1433 .map(|(msg, _, _, _)| msg)
1434 .unwrap_or_else(|| "transaction rejected as invalid during submission".to_string());
1435 QuorumDriverError::InvalidTransaction(IotaError::Unknown(format!(
1436 "Transaction was rejected as invalid by more than 1/3 of validator stake \
1437 during submission (non-retriable): {representative}"
1438 )))
1439 }
1440 Aborted {
1441 submission_retriable_errors,
1442 submission_non_retriable_errors,
1443 ..
1444 } => {
1445 let attempts = count_validator_attempts(&submission_retriable_errors)
1450 + count_validator_attempts(&submission_non_retriable_errors);
1451 QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
1452 total_attempts: attempts,
1453 }
1454 }
1455 other @ ForkedExecution { .. } => {
1456 let msg = other.to_string();
1460 error!("TransactionDriver observed forked execution: {msg}");
1461 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1462 }
1463 other @ ClientInternal { .. } => {
1464 let msg = other.to_string();
1465 warn!("TransactionDriver client-internal error: {msg}");
1466 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1467 }
1468 other @ SubmittedButFetchFailed { .. } => {
1469 let msg = other.to_string();
1470 warn!("TransactionDriver submitted transaction but failed to fetch effects: {msg}");
1471 QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1472 }
1473 }
1474}
1475
1476fn count_validator_attempts(errors: &AggregatedRequestErrors) -> u32 {
1477 errors
1478 .errors
1479 .iter()
1480 .map(|(_, authorities, _, _)| authorities.len() as u32)
1481 .sum()
1482}
1483
1484async fn join_submission_task<T>(
1487 handle: tokio::task::JoinHandle<Result<T, QuorumDriverError>>,
1488) -> Result<T, QuorumDriverError> {
1489 handle.await.unwrap_or_else(|e| {
1490 Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
1491 format!("transaction submission task panicked: {e}"),
1492 )))
1493 })
1494}
1495
1496#[derive(Clone)]
1498pub struct TransactionOrchestratorMetrics {
1499 total_req_received_single_writer: GenericCounter<AtomicU64>,
1500 total_req_received_shared_object: GenericCounter<AtomicU64>,
1501
1502 good_response_single_writer: GenericCounter<AtomicU64>,
1503 good_response_shared_object: GenericCounter<AtomicU64>,
1504
1505 req_in_flight_single_writer: GenericGauge<AtomicI64>,
1506 req_in_flight_shared_object: GenericGauge<AtomicI64>,
1507
1508 wait_for_finality_in_flight: GenericGauge<AtomicI64>,
1509 wait_for_finality_finished: GenericCounter<AtomicU64>,
1510 wait_for_finality_timeout: GenericCounter<AtomicU64>,
1511
1512 local_execution_in_flight: GenericGauge<AtomicI64>,
1513 local_execution_success: GenericCounter<AtomicU64>,
1514 local_execution_timeout: GenericCounter<AtomicU64>,
1515 local_execution_failure: GenericCounter<AtomicU64>,
1516
1517 early_cached_response: GenericCounter<AtomicU64>,
1518
1519 skip_effect_cert_events_cache_miss: GenericCounter<AtomicU64>,
1524
1525 skip_effect_cert_checkpoint_overrode_driver: GenericCounter<AtomicU64>,
1531
1532 request_latency_single_writer: Histogram,
1533 request_latency_shared_obj: Histogram,
1534 wait_for_finality_latency_single_writer: Histogram,
1535 wait_for_finality_latency_shared_obj: Histogram,
1536 local_execution_latency_single_writer: Histogram,
1537 local_execution_latency_shared_obj: Histogram,
1538}
1539
1540impl TransactionOrchestratorMetrics {
1544 pub fn new(registry: &Registry) -> Self {
1545 let total_req_received = register_int_counter_vec_with_registry!(
1546 "tx_orchestrator_total_req_received",
1547 "Total number of executions request Transaction Orchestrator receives, group by tx type",
1548 &["tx_type"],
1549 registry;
1550 MetricLevel::Warn,
1551 )
1552 .unwrap();
1553
1554 let total_req_received_single_writer =
1555 total_req_received.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1556 let total_req_received_shared_object =
1557 total_req_received.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1558
1559 let good_response = register_int_counter_vec_with_registry!(
1560 "tx_orchestrator_good_response",
1561 "Total number of good responses Transaction Orchestrator generates, group by tx type",
1562 &["tx_type"],
1563 registry;
1564 MetricLevel::Warn,
1565 )
1566 .unwrap();
1567
1568 let good_response_single_writer =
1569 good_response.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1570 let good_response_shared_object = good_response.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1571
1572 let req_in_flight = register_int_gauge_vec_with_registry!(
1573 "tx_orchestrator_req_in_flight",
1574 "Number of requests in flights Transaction Orchestrator processes, group by tx type",
1575 &["tx_type"],
1576 registry;
1577 MetricLevel::Warn,
1578 )
1579 .unwrap();
1580
1581 let req_in_flight_single_writer =
1582 req_in_flight.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1583 let req_in_flight_shared_object = req_in_flight.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1584
1585 let request_latency = register_histogram_vec_with_registry!(
1586 "tx_orchestrator_request_latency",
1587 "Time spent in processing one Transaction Orchestrator request",
1588 &["tx_type"],
1589 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1590 registry;
1591 MetricLevel::Warn,
1592 )
1593 .unwrap();
1594 let wait_for_finality_latency = register_histogram_vec_with_registry!(
1595 "tx_orchestrator_wait_for_finality_latency",
1596 "Time spent in waiting for one Transaction Orchestrator request gets finalized",
1597 &["tx_type"],
1598 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1599 registry;
1600 MetricLevel::Warn,
1601 )
1602 .unwrap();
1603 let local_execution_latency = register_histogram_vec_with_registry!(
1604 "tx_orchestrator_local_execution_latency",
1605 "Time spent in waiting for one Transaction Orchestrator gets locally executed",
1606 &["tx_type"],
1607 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1608 registry;
1609 MetricLevel::Warn,
1610 )
1611 .unwrap();
1612
1613 Self {
1614 total_req_received_single_writer,
1615 total_req_received_shared_object,
1616 good_response_single_writer,
1617 good_response_shared_object,
1618 req_in_flight_single_writer,
1619 req_in_flight_shared_object,
1620 wait_for_finality_in_flight: register_int_gauge_with_registry!(
1621 "tx_orchestrator_wait_for_finality_in_flight",
1622 "Number of in flight txns Transaction Orchestrator are waiting for finality for",
1623 registry;
1624 MetricLevel::Warn,
1625 )
1626 .unwrap(),
1627 wait_for_finality_finished: register_int_counter_with_registry!(
1628 "tx_orchestrator_wait_for_finality_finished",
1629 "Total number of txns Transaction Orchestrator gets responses from Quorum Driver before timeout, either success or failure",
1630 registry;
1631 MetricLevel::Warn,
1632 )
1633 .unwrap(),
1634 wait_for_finality_timeout: register_int_counter_with_registry!(
1635 "tx_orchestrator_wait_for_finality_timeout",
1636 "Total number of txns timing out in waiting for finality Transaction Orchestrator handles",
1637 registry;
1638 MetricLevel::Warn,
1639 )
1640 .unwrap(),
1641 local_execution_in_flight: register_int_gauge_with_registry!(
1642 "tx_orchestrator_local_execution_in_flight",
1643 "Number of local execution txns in flights Transaction Orchestrator handles",
1644 registry;
1645 MetricLevel::Warn,
1646 )
1647 .unwrap(),
1648 local_execution_success: register_int_counter_with_registry!(
1649 "tx_orchestrator_local_execution_success",
1650 "Total number of successful local execution txns Transaction Orchestrator handles",
1651 registry;
1652 MetricLevel::Warn,
1653 )
1654 .unwrap(),
1655 local_execution_timeout: register_int_counter_with_registry!(
1656 "tx_orchestrator_local_execution_timeout",
1657 "Total number of timed-out local execution txns Transaction Orchestrator handles",
1658 registry;
1659 MetricLevel::Warn,
1660 )
1661 .unwrap(),
1662 local_execution_failure: register_int_counter_with_registry!(
1663 "tx_orchestrator_local_execution_failure",
1664 "Total number of failed local execution txns Transaction Orchestrator handles",
1665 registry;
1666 MetricLevel::Warn,
1667 )
1668 .unwrap(),
1669 early_cached_response: register_int_counter_with_registry!(
1670 "tx_orchestrator_early_cached_response",
1671 "Total number of requests returning cached results for already-executed transactions",
1672 registry,
1673 )
1674 .unwrap(),
1675 skip_effect_cert_events_cache_miss: register_int_counter_with_registry!(
1676 "tx_orchestrator_skip_effect_cert_events_cache_miss",
1677 "Number of skip-effect-certification responses rejected because the \
1678 single submitter claimed to have events but the local cache did not \
1679 corroborate them",
1680 registry,
1681 )
1682 .unwrap(),
1683 skip_effect_cert_checkpoint_overrode_driver: register_int_counter_with_registry!(
1684 "tx_orchestrator_skip_effect_cert_checkpoint_overrode_driver",
1685 "Number of skip-effect-certification requests where local checkpoint \
1686 inclusion completed before the TransactionDriver call returned; the \
1687 driver future was cancelled and the response was rebuilt from cache",
1688 registry,
1689 )
1690 .unwrap(),
1691 request_latency_single_writer: request_latency
1692 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1693 request_latency_shared_obj: request_latency.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1694 wait_for_finality_latency_single_writer: wait_for_finality_latency
1695 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1696 wait_for_finality_latency_shared_obj: wait_for_finality_latency
1697 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1698 local_execution_latency_single_writer: local_execution_latency
1699 .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1700 local_execution_latency_shared_obj: local_execution_latency
1701 .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1702 }
1703 }
1704
1705 pub fn new_for_tests() -> Self {
1706 let registry = Registry::new();
1707 Self::new(®istry)
1708 }
1709}
1710
1711#[async_trait::async_trait]
1712impl<A> iota_types::transaction_executor::TransactionExecutor for TransactionOrchestrator<A>
1713where
1714 A: AuthorityAPI + Send + Sync + 'static + Clone,
1715{
1716 async fn execute_transaction(
1717 &self,
1718 request: ExecuteTransactionRequestV1,
1719 skip_certification: bool,
1720 client_addr: Option<std::net::SocketAddr>,
1721 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
1722 self.execute_transaction_v1(request, skip_certification, client_addr)
1723 .await
1724 }
1725
1726 fn simulate_transaction(
1727 &self,
1728 transaction: Transaction,
1729 checks: VmChecks,
1730 ) -> Result<SimulateTransactionResult, IotaError> {
1731 self.validator_state
1732 .simulate_transaction(transaction, checks)
1733 }
1734
1735 async fn wait_for_checkpoint_inclusion(
1742 &self,
1743 digests: &[TransactionDigest],
1744 timeout: Duration,
1745 ) -> Result<BTreeMap<TransactionDigest, (CheckpointSequenceNumber, u64)>, IotaError> {
1746 self.validator_state
1747 .wait_for_checkpoint_inclusion(digests, timeout)
1748 .await
1749 }
1750
1751 fn read_transaction_from_cache(
1752 &self,
1753 digest: &TransactionDigest,
1754 include_events: bool,
1755 include_input_objects: bool,
1756 include_output_objects: bool,
1757 ) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1758 read_cached_transaction_data(
1759 &self.validator_state,
1760 digest,
1761 include_events,
1762 include_input_objects,
1763 include_output_objects,
1764 )
1765 }
1766}
1767
1768fn read_cached_transaction_data(
1773 validator_state: &Arc<AuthorityState>,
1774 digest: &TransactionDigest,
1775 include_events: bool,
1776 include_input_objects: bool,
1777 include_output_objects: bool,
1778) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1779 let cache = validator_state.get_transaction_cache_reader();
1780 let Some(effects) = cache.try_get_executed_effects(digest)? else {
1781 return Ok(None);
1782 };
1783
1784 let events = if include_events && effects.events_digest().is_some() {
1785 Some(validator_state.get_transaction_events(digest)?)
1786 } else {
1787 None
1788 };
1789
1790 let input_objects = if include_input_objects {
1791 Some(
1792 validator_state
1793 .get_transaction_input_objects(&effects)
1794 .map_err(|e| IotaError::Unknown(format!("input objects: {e:?}")))?,
1795 )
1796 } else {
1797 None
1798 };
1799 let output_objects = if include_output_objects {
1800 Some(
1801 validator_state
1802 .get_transaction_output_objects(&effects)
1803 .map_err(|e| IotaError::Unknown(format!("output objects: {e:?}")))?,
1804 )
1805 } else {
1806 None
1807 };
1808
1809 Ok(Some(
1810 iota_types::transaction_executor::CachedTransactionData {
1811 effects,
1812 events,
1813 input_objects,
1814 output_objects,
1815 },
1816 ))
1817}
1818
1819type InFlightSubmissionResult = Result<Arc<QuorumTransactionResponse>, QuorumDriverError>;
1826
1827type InFlightTransactions =
1831 Arc<Mutex<HashMap<TransactionDigest, watch::Sender<Option<InFlightSubmissionResult>>>>>;
1832
1833enum TransactionSubmission {
1838 Driving(TransactionSubmissionGuard),
1839 AlreadyInFlight(watch::Receiver<Option<InFlightSubmissionResult>>),
1840}
1841
1842struct TransactionSubmissionGuard {
1853 in_flight_transactions: InFlightTransactions,
1854 tx_digest: TransactionDigest,
1855}
1856
1857impl TransactionSubmissionGuard {
1858 fn acquire(
1859 in_flight_transactions: InFlightTransactions,
1860 tx_digest: TransactionDigest,
1861 ) -> TransactionSubmission {
1862 {
1863 let mut in_flight = in_flight_transactions.lock();
1864 match in_flight.entry(tx_digest) {
1865 Entry::Occupied(entry) => {
1866 return TransactionSubmission::AlreadyInFlight(entry.get().subscribe());
1867 }
1868 Entry::Vacant(entry) => {
1869 let (sender, _initial_receiver) = watch::channel(None);
1870 entry.insert(sender);
1871 debug!(?tx_digest, "added transaction to in-flight map");
1872 }
1873 }
1874 }
1875 TransactionSubmission::Driving(Self {
1876 in_flight_transactions,
1877 tx_digest,
1878 })
1879 }
1880
1881 fn publish(&self, result: InFlightSubmissionResult) {
1886 if let Some(sender) = self.in_flight_transactions.lock().get(&self.tx_digest) {
1887 sender.send_replace(Some(result));
1888 }
1889 }
1890}
1891
1892impl Drop for TransactionSubmissionGuard {
1893 fn drop(&mut self) {
1894 self.in_flight_transactions.lock().remove(&self.tx_digest);
1895 }
1896}
1897
1898#[cfg(test)]
1899mod tests {
1900 use super::*;
1901
1902 fn acquire_driving(
1903 in_flight: &InFlightTransactions,
1904 tx_digest: TransactionDigest,
1905 ) -> TransactionSubmissionGuard {
1906 match TransactionSubmissionGuard::acquire(in_flight.clone(), tx_digest) {
1907 TransactionSubmission::Driving(guard) => guard,
1908 TransactionSubmission::AlreadyInFlight(_) => {
1909 panic!("expected to acquire the driving submission")
1910 }
1911 }
1912 }
1913
1914 fn acquire_duplicate(
1915 in_flight: &InFlightTransactions,
1916 tx_digest: TransactionDigest,
1917 ) -> watch::Receiver<Option<InFlightSubmissionResult>> {
1918 match TransactionSubmissionGuard::acquire(in_flight.clone(), tx_digest) {
1919 TransactionSubmission::Driving(_) => {
1920 panic!("expected the digest to already be in flight")
1921 }
1922 TransactionSubmission::AlreadyInFlight(receiver) => receiver,
1923 }
1924 }
1925
1926 #[tokio::test]
1927 async fn duplicate_submission_receives_published_outcome() {
1928 let in_flight = InFlightTransactions::default();
1929 let tx_digest = TransactionDigest::random();
1930
1931 let guard = acquire_driving(&in_flight, tx_digest);
1932 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
1933
1934 guard.publish(Err(QuorumDriverError::TimeoutBeforeFinality));
1935 drop(guard);
1936
1937 let outcome = receiver
1940 .wait_for(|outcome| outcome.is_some())
1941 .await
1942 .expect("outcome was published before the sender dropped")
1943 .clone()
1944 .expect("wait_for only returns once the outcome is Some");
1945 assert!(matches!(
1946 outcome,
1947 Err(QuorumDriverError::TimeoutBeforeFinality)
1948 ));
1949 assert!(
1950 in_flight.lock().is_empty(),
1951 "guard drop must remove the in-flight entry"
1952 );
1953 }
1954
1955 #[tokio::test]
1956 async fn duplicate_subscribing_after_publish_receives_outcome() {
1957 let in_flight = InFlightTransactions::default();
1958 let tx_digest = TransactionDigest::random();
1959
1960 let guard = acquire_driving(&in_flight, tx_digest);
1961 guard.publish(Err(QuorumDriverError::TimeoutBeforeFinality));
1962
1963 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
1967 drop(guard);
1968
1969 let outcome = receiver
1970 .wait_for(|outcome| outcome.is_some())
1971 .await
1972 .expect("the outcome is stored in the channel regardless of subscribers")
1973 .clone()
1974 .expect("wait_for only returns once the outcome is Some");
1975 assert!(matches!(
1976 outcome,
1977 Err(QuorumDriverError::TimeoutBeforeFinality)
1978 ));
1979 }
1980
1981 #[tokio::test]
1982 async fn dropped_guard_without_outcome_closes_channel() {
1983 let in_flight = InFlightTransactions::default();
1984 let tx_digest = TransactionDigest::random();
1985
1986 let guard = acquire_driving(&in_flight, tx_digest);
1987 let mut receiver = acquire_duplicate(&in_flight, tx_digest);
1988 drop(guard);
1989
1990 receiver
1991 .wait_for(|outcome| outcome.is_some())
1992 .await
1993 .expect_err("dropping the guard without publishing must close the channel");
1994 assert!(in_flight.lock().is_empty());
1995
1996 let _guard = acquire_driving(&in_flight, tx_digest);
1998 }
1999}