Skip to main content

iota_core/
transaction_orchestrator.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5// Transaction Orchestrator is a Node component that utilizes Quorum Driver (or
6// optionally TransactionDriver) to submit transactions to validators for
7// finality, and proactively executes finalized transactions locally.
8
9use std::{
10    collections::BTreeMap, net::SocketAddr, ops::Deref, path::Path, sync::Arc, time::Duration,
11};
12
13use futures::{
14    FutureExt,
15    future::{Either, Future, select},
16};
17use iota_common::{debug_fatal, sync::notify_read::NotifyRead};
18use iota_config::NodeConfig;
19use iota_metrics::{
20    TX_TYPE_SHARED_OBJ_TX, TX_TYPE_SINGLE_WRITER_TX, add_server_timing,
21    spawn_logged_monitored_task, spawn_monitored_task,
22};
23use iota_storage::write_path_pending_tx_log::WritePathPendingTransactionLog;
24use iota_types::{
25    base_types::TransactionDigest,
26    effects::TransactionEffectsAPI,
27    error::{IotaError, IotaResult},
28    iota_system_state::IotaSystemState,
29    messages_checkpoint::CheckpointSequenceNumber,
30    quorum_driver_types::{
31        EffectsFinalityInfo, ExecuteTransactionRequestType, ExecuteTransactionRequestV1,
32        ExecuteTransactionResponseV1, FinalizedEffects, IsTransactionExecutedLocally,
33        QuorumDriverEffectsQueueResult, QuorumDriverError, QuorumDriverResponse,
34        QuorumDriverResult,
35    },
36    transaction::{TransactionData, VerifiedTransaction},
37    transaction_driver_types::{
38        EffectsFinalityInfo as TdEffectsFinalityInfo, FinalizedEffects as TdFinalizedEffects,
39    },
40    transaction_executor::{SimulateTransactionResult, VmChecks},
41};
42use prometheus_filtered::{
43    Histogram, Registry,
44    core::{AtomicI64, AtomicU64, GenericCounter, GenericGauge},
45    register_histogram_vec_with_registry, register_int_counter_vec_with_registry,
46    register_int_counter_with_registry, register_int_gauge_vec_with_registry,
47    register_int_gauge_with_registry,
48};
49use tokio::{
50    sync::broadcast::{Receiver, error::RecvError},
51    task::JoinHandle,
52    time::timeout,
53};
54use tracing::{Instrument, debug, error, info, instrument, trace_span, warn};
55
56use crate::{
57    authority::{AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore},
58    authority_aggregator::AuthorityAggregator,
59    authority_client::{AuthorityAPI, NetworkAuthorityClient},
60    quorum_driver::{
61        QuorumDriverHandler, QuorumDriverHandlerBuilder, QuorumDriverMetrics,
62        reconfig_observer::{OnsiteReconfigObserver, ReconfigObserver},
63    },
64    transaction_driver::{
65        AggregatedRequestErrors, QuorumTransactionResponse, SubmitTransactionOptions,
66        TransactionDriver, TransactionDriverError, TransactionDriverMetrics,
67        reconfig_observer::OnsiteReconfigObserver as TdOnsiteReconfigObserver,
68    },
69    validator_client_monitor::ValidatorClientMetrics,
70};
71
72// How long to wait for local execution (including parents) before a timeout
73// is returned to client.
74const LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(10);
75
76const WAIT_FOR_FINALITY_TIMEOUT: Duration = Duration::from_secs(30);
77
78/// The submission flow used to drive transactions to finality. Exactly one
79/// flow is active, selected by the P-COOL protocol flag at construction
80/// time.
81enum Driver<A: Clone> {
82    /// Certificate-based flow (P-COOL disabled).
83    Quorum(Arc<QuorumDriverHandler<A>>),
84    /// Direct-to-consensus P-COOL flow.
85    Transaction(Arc<TransactionDriver<A>>),
86}
87
88/// Transaction Orchestrator is a Node component that supports both QuorumDriver
89/// and TransactionDriver for submitting transactions to validators for
90/// finality. It adds inflight deduplication, waiting for local execution,
91/// recovery, and epoch change handling.
92pub struct TransactionOrchestrator<A: Clone> {
93    driver: Driver<A>,
94    validator_state: Arc<AuthorityState>,
95    _local_executor_handle: Option<JoinHandle<()>>,
96    pending_tx_log: Arc<WritePathPendingTransactionLog>,
97    notifier: Arc<NotifyRead<TransactionDigest, QuorumDriverResult>>,
98    metrics: Arc<TransactionOrchestratorMetrics>,
99}
100
101impl TransactionOrchestrator<NetworkAuthorityClient> {
102    pub fn new_with_auth_aggregator(
103        validators: Arc<AuthorityAggregator<NetworkAuthorityClient>>,
104        validator_state: Arc<AuthorityState>,
105        reconfig_channel: Receiver<IotaSystemState>,
106        parent_path: &Path,
107        prometheus_registry: &Registry,
108        node_config: Option<&NodeConfig>,
109    ) -> Self {
110        // Check protocol config to determine if P-COOL flow is enabled
111        let epoch_store = validator_state.load_epoch_store_one_call_per_task();
112        let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
113
114        // Create TransactionDriver reconfig observer only if P-COOL is enabled
115        let td_reconfig_observer = if use_transaction_driver {
116            Some(TdOnsiteReconfigObserver::new(
117                reconfig_channel.resubscribe(),
118                validator_state.get_object_cache_reader().clone(),
119                validator_state.clone_committee_store(),
120                validators.safe_client_metrics_base.clone(),
121            ))
122        } else {
123            None
124        };
125
126        // Create QuorumDriver reconfig observer only if P-COOL is NOT enabled
127        let qd_reconfig_observer = if !use_transaction_driver {
128            Some(OnsiteReconfigObserver::new(
129                reconfig_channel.resubscribe(),
130                validator_state.get_object_cache_reader().clone(),
131                validator_state.clone_committee_store(),
132                validators.safe_client_metrics_base.clone(),
133                validators.metrics.deref().clone(),
134            ))
135        } else {
136            None
137        };
138
139        TransactionOrchestrator::new(
140            validators,
141            validator_state,
142            parent_path,
143            prometheus_registry,
144            qd_reconfig_observer,
145            td_reconfig_observer,
146            node_config,
147        )
148    }
149}
150
151impl<A> TransactionOrchestrator<A>
152where
153    A: AuthorityAPI + Send + Sync + 'static + Clone,
154    OnsiteReconfigObserver: ReconfigObserver<A>,
155    TdOnsiteReconfigObserver: crate::transaction_driver::reconfig_observer::ReconfigObserver<A>,
156{
157    pub fn new(
158        validators: Arc<AuthorityAggregator<A>>,
159        validator_state: Arc<AuthorityState>,
160        parent_path: &Path,
161        prometheus_registry: &Registry,
162        reconfig_observer: Option<OnsiteReconfigObserver>,
163        td_reconfig_observer: Option<TdOnsiteReconfigObserver>,
164        node_config: Option<&NodeConfig>,
165    ) -> Self {
166        // Check protocol config to determine if P-COOL flow is enabled
167        let epoch_store = validator_state.load_epoch_store_one_call_per_task();
168        let use_transaction_driver = epoch_store.protocol_config().enable_pcool_flow();
169
170        let notifier = Arc::new(NotifyRead::new());
171        let metrics = Arc::new(TransactionOrchestratorMetrics::new(prometheus_registry));
172        let pending_tx_log = Arc::new(WritePathPendingTransactionLog::new(
173            parent_path.join("fullnode_pending_transactions"),
174        ));
175
176        let (driver, _local_executor_handle) = if !use_transaction_driver {
177            let qd_metrics = Arc::new(QuorumDriverMetrics::new(prometheus_registry));
178            let reconfig_observer = Arc::new(
179                reconfig_observer
180                    .expect("QuorumDriver reconfig observer required when P-COOL is disabled"),
181            );
182            let handler = Arc::new(
183                QuorumDriverHandlerBuilder::new(validators, qd_metrics)
184                    .with_notifier(notifier.clone())
185                    .with_reconfig_observer(reconfig_observer)
186                    .start(),
187            );
188            let effects_receiver = handler.subscribe_to_effects();
189            let pending_tx_log_clone = pending_tx_log.clone();
190            let local_executor_handle = spawn_monitored_task!(async move {
191                Self::loop_pending_transaction_log(effects_receiver, pending_tx_log_clone).await;
192            });
193            // Pending-transaction recovery is QuorumDriver-only; the
194            // TransactionDriver goes directly to consensus and tracks no
195            // pending certificates.
196            Self::schedule_txes_in_log(pending_tx_log.clone(), handler.clone());
197            (Driver::Quorum(handler), Some(local_executor_handle))
198        } else {
199            let td_metrics = Arc::new(TransactionDriverMetrics::new(prometheus_registry));
200            let client_metrics = Arc::new(ValidatorClientMetrics::new(prometheus_registry));
201            let observer = td_reconfig_observer
202                .expect("TransactionDriver reconfig observer required when P-COOL is enabled");
203            (
204                Driver::Transaction(TransactionDriver::new(
205                    validators,
206                    Arc::new(observer),
207                    td_metrics,
208                    node_config,
209                    client_metrics,
210                )),
211                None,
212            )
213        };
214
215        Self {
216            driver,
217            validator_state,
218            _local_executor_handle,
219            pending_tx_log,
220            notifier,
221            metrics,
222        }
223    }
224}
225
226impl<A> TransactionOrchestrator<A>
227where
228    A: AuthorityAPI + Send + Sync + 'static + Clone,
229{
230    #[instrument(name = "tx_orchestrator_execute_transaction_block", level = "trace", skip_all,
231    fields(
232        tx_digest = ?request.transaction.digest(),
233        tx_type = ?request_type,
234    ),
235    err)]
236    pub async fn execute_transaction_block(
237        &self,
238        request: ExecuteTransactionRequestV1,
239        request_type: ExecuteTransactionRequestType,
240        client_addr: Option<SocketAddr>,
241    ) -> Result<(ExecuteTransactionResponseV1, IsTransactionExecutedLocally), QuorumDriverError>
242    {
243        let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
244
245        // Captured before `request` moves so the skip-cert reconcile reads
246        // caller intent, not whatever the submitter happened to return — a
247        // Byzantine submitter could otherwise censor a field by returning
248        // `None`.
249        let include_events = request.include_events;
250        let include_input_objects = request.include_input_objects;
251        let include_output_objects = request.include_output_objects;
252
253        let transaction = epoch_store
254            .verify_transaction(request.transaction.clone())
255            .map_err(QuorumDriverError::InvalidUserSignature)?;
256
257        let wait_for_local_execution = matches!(
258            request_type,
259            ExecuteTransactionRequestType::WaitForLocalExecution
260        );
261        let tx_digest = *transaction.digest();
262
263        let (mut response, seq) = match (&self.driver, wait_for_local_execution) {
264            (Driver::Transaction(td), true) => {
265                self.submit_with_checkpoint_race(td.clone(), request, client_addr, tx_digest)
266                    .await?
267            }
268            (Driver::Transaction(td), false) => (
269                Some(
270                    self.submit_with_transaction_driver(td.clone(), request, client_addr, false)
271                        .await
272                        .map_err(map_td_error_to_qd)?,
273                ),
274                None,
275            ),
276            (Driver::Quorum(qd), _) => {
277                let (_, qd_resp) = self
278                    .execute_transaction_impl(qd, &epoch_store, request, client_addr)
279                    .await?;
280                (Some(quorum_driver_response_to_v1(qd_resp)), None)
281            }
282        };
283
284        // `needs_cache_rebuild` is derived from finality, not caller intent:
285        // the QD fallback path returns `Certified` (no rebuild needed) even
286        // when the caller asked for `WaitForLocalExecution`, while only the
287        // TD skip-cert engine produces `UncertifiedSingleValidator`. The
288        // checkpoint sequence comes from `submit_with_checkpoint_race`, which
289        // relies on `executed_transactions_to_checkpoint` being written
290        // strictly after every tx's effects — so a `Some(seq)` here implies
291        // the cache has authoritative effects.
292        let needs_cache_rebuild = matches!(
293            response.as_ref().map(|r| &r.effects.finality_info),
294            None | Some(EffectsFinalityInfo::UncertifiedSingleValidator(_)),
295        );
296
297        let executed_locally = if !wait_for_local_execution {
298            false
299        } else if needs_cache_rebuild {
300            let Some(seq) = seq else {
301                // Timed out waiting for the tx to land in a local checkpoint.
302                // In this branch `response` is either `None` (recovery) or
303                // `UncertifiedSingleValidator` (TD skip-cert) — both must
304                // surface as `TimeoutBeforeFinality` rather than leaking
305                // uncorroborated single-validator effects to the client.
306                return Err(QuorumDriverError::TimeoutBeforeFinality);
307            };
308            match response.as_mut() {
309                Some(existing) => Self::reconcile_effects_from_cache(
310                    &self.validator_state,
311                    tx_digest,
312                    seq,
313                    include_events,
314                    include_input_objects,
315                    include_output_objects,
316                    existing,
317                    &self.metrics,
318                )?,
319                None => {
320                    response = Some(Self::build_response_from_cache(
321                        &self.validator_state,
322                        tx_digest,
323                        seq,
324                        include_events,
325                        include_input_objects,
326                        include_output_objects,
327                    )?);
328                }
329            }
330            true
331        } else {
332            // QD path: response is already 2f+1 certified, just confirm local
333            // execution finished. Removable once QD is dropped from the
334            // fullnode.
335            let ok = Self::wait_for_finalized_tx_executed_locally_with_timeout(
336                &self.validator_state,
337                &transaction,
338                &self.metrics,
339            )
340            .await
341            .is_ok();
342            add_server_timing("local_execution");
343            ok
344        };
345
346        let response = response.expect("response must be populated before return");
347
348        // Safety guard: `UncertifiedSingleValidator` finality carries effects
349        // from the single submitting validator only — they MUST NOT reach the
350        // client without first being corroborated against the local cache. The
351        // reachable paths today all either upgrade finality via
352        // `reconcile_effects_from_cache` / `build_response_from_cache`, or
353        // branch to `TimeoutBeforeFinality`; this guard is the last-chance
354        // fallback for a future refactor that forgets to reconcile. Do not
355        // remove as dead code.
356        if matches!(
357            response.effects.finality_info,
358            EffectsFinalityInfo::UncertifiedSingleValidator(_)
359        ) {
360            debug_fatal!(
361                "Uncertified effects (UncertifiedSingleValidator) about to be returned \
362                 to the client for tx {:?}",
363                response.effects.effects.transaction_digest()
364            );
365            return Err(QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(
366                "internal error: transaction effects not finalized".to_string(),
367            )));
368        }
369
370        Ok((response, executed_locally))
371    }
372
373    /// Replace the response's effects, events, and input/output objects with
374    /// the authoritative copies derived from the local cache — the local
375    /// checkpoint executor has processed the tx, so the cache has the real
376    /// data and the TD-returned (single-validator) copies can be discarded.
377    ///
378    /// `tx_digest` must be the digest of the caller's original transaction,
379    /// not the digest carried in `response.effects.effects` — a byzantine
380    /// submitter could set the latter to an unrelated (already-executed) tx
381    /// so we'd read unrelated effects from the cache.
382    ///
383    /// The caller must have obtained `checkpoint_seq` from
384    /// `wait_for_checkpoint_inclusion` (not just `get_transaction_checkpoint`),
385    /// because that function guarantees both the effects write and the
386    /// checkpoint-mapping write have landed — it's the only way to avoid the
387    /// race between `notify_read_executed_effects_digests` (fires per-tx) and
388    /// `insert_finalized_transactions` (fires per-checkpoint, after the
389    /// `CheckpointExecutor` has awaited every tx in that checkpoint).
390    ///
391    /// Upgrades the finality info to `Checkpointed(epoch, checkpoint_seq)`. A
392    /// warning is logged if the TD-returned effects digest diverges from the
393    /// cache digest, or the submitter claimed events the cache doesn't have
394    /// (byzantine submitter or bug).
395    fn reconcile_effects_from_cache(
396        validator_state: &Arc<AuthorityState>,
397        tx_digest: TransactionDigest,
398        checkpoint_seq: CheckpointSequenceNumber,
399        include_events: bool,
400        include_input_objects: bool,
401        include_output_objects: bool,
402        response: &mut ExecuteTransactionResponseV1,
403        metrics: &TransactionOrchestratorMetrics,
404    ) -> Result<(), QuorumDriverError> {
405        let rebuilt = Self::build_response_from_cache(
406            validator_state,
407            tx_digest,
408            checkpoint_seq,
409            include_events,
410            include_input_objects,
411            include_output_objects,
412        )?;
413
414        let td_digest = response.effects.effects.digest();
415        let cache_digest = rebuilt.effects.effects.digest();
416        if td_digest != cache_digest {
417            warn!(
418                ?tx_digest,
419                ?td_digest,
420                ?cache_digest,
421                "reconcile_effects_from_cache: TransactionDriver and local cache disagree \
422                 on effects digest — replacing with cache (possible byzantine submitter)"
423            );
424        }
425        if include_events && response.events.is_some() && rebuilt.events.is_none() {
426            warn!(
427                ?tx_digest,
428                "reconcile_effects_from_cache: submitter claimed events but cache has \
429                 none — discarding (possible byzantine submitter)"
430            );
431            metrics.skip_effect_cert_events_cache_miss.inc();
432        }
433        *response = rebuilt;
434        Ok(())
435    }
436
437    /// Build a skip-effect-certification response entirely from the local
438    /// cache. The caller must have already obtained `checkpoint_seq` via
439    /// `wait_for_checkpoint_inclusion`, which is supposed to guarantee both
440    /// the effects write and the checkpoint-mapping write have landed. A
441    /// missing cache entry here would mean a transient races we observed in
442    /// practice; mapped to `TimeoutBeforeFinality` so the client retries
443    /// rather than seeing a misleading `QuorumDriverInternal`.
444    fn build_response_from_cache(
445        validator_state: &Arc<AuthorityState>,
446        tx_digest: TransactionDigest,
447        checkpoint_seq: CheckpointSequenceNumber,
448        include_events: bool,
449        include_input_objects: bool,
450        include_output_objects: bool,
451    ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
452        let cached = read_cached_transaction_data(
453            validator_state,
454            &tx_digest,
455            include_events,
456            include_input_objects,
457            include_output_objects,
458        )
459        .map_err(|e| {
460            QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(format!(
461                "failed to read cached tx data for {tx_digest:?}: {e:?}"
462            )))
463        })?
464        .ok_or_else(|| {
465            // Checkpoint inclusion is supposed to guarantee the cache has
466            // effects, but we've seen transient misses; surface as a retriable
467            // timeout rather than an internal error.
468            warn!(
469                ?tx_digest,
470                "effects missing from cache after checkpoint inclusion — surfacing as \
471                 TimeoutBeforeFinality"
472            );
473            QuorumDriverError::TimeoutBeforeFinality
474        })?;
475        let iota_types::transaction_executor::CachedTransactionData {
476            effects,
477            events,
478            input_objects,
479            output_objects,
480        } = cached;
481
482        let epoch = effects.epoch();
483        Ok(ExecuteTransactionResponseV1 {
484            effects: FinalizedEffects {
485                effects,
486                finality_info: EffectsFinalityInfo::Checkpointed(epoch, checkpoint_seq),
487            },
488            events,
489            input_objects,
490            output_objects,
491            auxiliary_data: None,
492        })
493    }
494
495    // Utilize the handle_certificate_v1 validator api to request input/output
496    // objects
497    #[instrument(name = "tx_orchestrator_execute_transaction_v1", level = "trace", skip_all,
498                 fields(tx_digest = ?request.transaction.digest()))]
499    pub async fn execute_transaction_v1(
500        &self,
501        request: ExecuteTransactionRequestV1,
502        skip_certification: bool,
503        client_addr: Option<SocketAddr>,
504    ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
505        let epoch_store = self.validator_state.load_epoch_store_one_call_per_task();
506
507        match &self.driver {
508            Driver::Transaction(td) => {
509                // v1 does not do an internal wait; callers (e.g. the gRPC
510                // execution service) are responsible for their own
511                // `wait_for_checkpoint_inclusion` when they need it, and will
512                // reconcile the response from the cache there.
513                epoch_store
514                    .verify_transaction(request.transaction.clone())
515                    .map_err(QuorumDriverError::InvalidUserSignature)?;
516                self.submit_with_transaction_driver(
517                    td.clone(),
518                    request,
519                    client_addr,
520                    skip_certification,
521                )
522                .await
523                .map_err(map_td_error_to_qd)
524            }
525            Driver::Quorum(qd) => {
526                let qd_resp = self
527                    .execute_transaction_impl(qd, &epoch_store, request, client_addr)
528                    .await
529                    .map(|(_, r)| r)?;
530                Ok(quorum_driver_response_to_v1(qd_resp))
531            }
532        }
533    }
534
535    /// Submit on the skip-effect-certification path while concurrently
536    /// waiting for local checkpoint inclusion. The race is asymmetric:
537    ///
538    /// - If the **checkpoint** future resolves first (slow driver, e.g. stuck
539    ///   corroborating a Byzantine validator's rejection), the driver future is
540    ///   dropped and the caller rebuilds the response from the local cache.
541    /// - If the **driver** returns first, its result is taken and the
542    ///   checkpoint future is awaited to completion (up to the shared
543    ///   `WAIT_FOR_FINALITY_TIMEOUT`) before returning, so the caller has a
544    ///   checkpoint sequence to reconcile against.
545    ///
546    /// Returns `(response, seq)` where `response` is `Some` when the driver
547    /// returned a result (which may carry `UncertifiedSingleValidator`
548    /// finality requiring rebuild) and `seq` is the checkpoint sequence if
549    /// either future yielded it.
550    #[instrument(name = "tx_orchestrator_submit_with_checkpoint_race", level = "trace", skip_all,
551                 fields(tx_digest = ?tx_digest))]
552    async fn submit_with_checkpoint_race(
553        &self,
554        td: Arc<TransactionDriver<A>>,
555        request: ExecuteTransactionRequestV1,
556        client_addr: Option<SocketAddr>,
557        tx_digest: TransactionDigest,
558    ) -> Result<
559        (
560            Option<ExecuteTransactionResponseV1>,
561            Option<CheckpointSequenceNumber>,
562        ),
563        QuorumDriverError,
564    > {
565        let digests = [tx_digest];
566        let checkpoint_inclusion = self
567            .validator_state
568            .wait_for_checkpoint_inclusion(&digests, WAIT_FOR_FINALITY_TIMEOUT);
569        tokio::pin!(checkpoint_inclusion);
570        let driver = self.submit_with_transaction_driver(td, request, client_addr, true);
571
572        let seq_for_tx = |inclusion_map: BTreeMap<_, (CheckpointSequenceNumber, _)>| {
573            inclusion_map.get(&tx_digest).map(|&(seq, _)| seq)
574        };
575
576        let result = tokio::select! {
577            biased;
578            // `SubmittedButFetchFailed` is retriable (`ErrorCategory::Unavailable`)
579            // so the driver's outer loop reissues submission internally and
580            // only returns here as `Ok`, `TimeoutWithLastRetriableError`, or
581            // a non-retriable error like `RejectedByValidators`.
582            driver_result = driver => {
583                let response = Some(driver_result.map_err(map_td_error_to_qd)?);
584                let seq = (&mut checkpoint_inclusion).await.ok().and_then(seq_for_tx);
585                (response, seq)
586            }
587            checkpoint_result = &mut checkpoint_inclusion => {
588                self.metrics.skip_effect_cert_checkpoint_overrode_driver.inc();
589                (None, checkpoint_result.ok().and_then(seq_for_tx))
590            }
591        };
592        add_server_timing("local_execution");
593        Ok(result)
594    }
595
596    /// Submit a transaction via the TransactionDriver (P-COOL flow).
597    ///
598    /// With `skip_certification = true` the driver may return
599    /// `UncertifiedSingleValidator` effects without a 2f+1 broadcast. The
600    /// caller (gRPC `execute_transactions` or `execute_transaction_block`)
601    /// is then responsible for `wait_for_checkpoint_inclusion` and the
602    /// cache-rebuild gate that replaces those single-validator effects with
603    /// authoritative data — uncertified data must never reach the client.
604    /// See `corroborate_single_validator_error` for the per-submission
605    /// fetch-failure recovery flow inside the driver.
606    #[instrument(name = "tx_orchestrator_submit_with_td", level = "trace", skip_all,
607                 fields(tx_digest = ?request.transaction.digest()))]
608    async fn submit_with_transaction_driver(
609        &self,
610        td: Arc<TransactionDriver<A>>,
611        request: ExecuteTransactionRequestV1,
612        client_addr: Option<SocketAddr>,
613        skip_certification: bool,
614    ) -> Result<ExecuteTransactionResponseV1, TransactionDriverError> {
615        let tx_digest = *request.transaction.digest();
616
617        // TODO: add transaction to some struct to prevent sending the same transaction
618        // multiple times in case client sends it multiple times if self
619        //     .pending_tx_log
620        //     .write_pending_transaction_maybe(&transaction)
621        //     .await
622        //     .map_err(|e| QuorumDriverError::QuorumDriverInternal(e))?
623        // {
624        //     debug!(?tx_digest, "no pending request in flight, submitting to
625        // TransactionDriver."); } else {
626        //     debug!(?tx_digest, "transaction already in flight, skipping duplicate
627        // submission."); }
628
629        let td_response = td
630            .drive_transaction(
631                Some(request.transaction.clone()),
632                SubmitTransactionOptions {
633                    forwarded_client_addr: client_addr,
634                    ..Default::default()
635                },
636                Some(WAIT_FOR_FINALITY_TIMEOUT),
637                skip_certification,
638            )
639            .await?;
640
641        debug!(
642            "TransactionOrchestrator: TransactionDriver submission succeeded for transaction {}",
643            tx_digest
644        );
645
646        let QuorumTransactionResponse {
647            effects: td_effects,
648            events,
649            input_objects,
650            output_objects,
651            auxiliary_data,
652        } = td_response;
653
654        let effects = convert_td_to_qd_effects(td_effects);
655        Ok(ExecuteTransactionResponseV1 {
656            effects,
657            events: if request.include_events { events } else { None },
658            input_objects: if request.include_input_objects {
659                input_objects
660            } else {
661                None
662            },
663            output_objects: if request.include_output_objects {
664                output_objects
665            } else {
666                None
667            },
668            auxiliary_data: if request.include_auxiliary_data {
669                auxiliary_data
670            } else {
671                None
672            },
673        })
674    }
675
676    // TODO check if tx is already executed on this node.
677    // Note: since EffectsCert is not stored today, we need to gather that from
678    // validators (and maybe store it for caching purposes)
679    #[instrument(level = "trace", skip_all, fields(tx_digest = ?request.transaction.digest()))]
680    async fn execute_transaction_impl(
681        &self,
682        quorum_driver: &Arc<QuorumDriverHandler<A>>,
683        epoch_store: &Arc<AuthorityPerEpochStore>,
684        request: ExecuteTransactionRequestV1,
685        client_addr: Option<SocketAddr>,
686    ) -> Result<(VerifiedTransaction, QuorumDriverResponse), QuorumDriverError> {
687        // Reject malformed transactions before any code path inspects shared
688        // inputs or `MoveAuthenticator`
689        request
690            .transaction
691            .validity_check(&epoch_store.tx_validity_check_context())
692            .map_err(QuorumDriverError::InvalidTransaction)?;
693        let transaction = epoch_store
694            .verify_transaction(request.transaction.clone())
695            .map_err(QuorumDriverError::InvalidUserSignature)?;
696        let (_in_flight_metrics_guards, good_response_metrics) = self.update_metrics(&transaction);
697        let tx_digest = *transaction.digest();
698        debug!(?tx_digest, "TO Received transaction execution request.");
699
700        let (_e2e_latency_timer, _txn_finality_timer) = if transaction.contains_shared_object() {
701            (
702                self.metrics.request_latency_shared_obj.start_timer(),
703                self.metrics
704                    .wait_for_finality_latency_shared_obj
705                    .start_timer(),
706            )
707        } else {
708            (
709                self.metrics.request_latency_single_writer.start_timer(),
710                self.metrics
711                    .wait_for_finality_latency_single_writer
712                    .start_timer(),
713            )
714        };
715
716        // TODO: refactor all the gauge and timer metrics with `monitored_scope`
717        let wait_for_finality_gauge = self.metrics.wait_for_finality_in_flight.clone();
718        wait_for_finality_gauge.inc();
719        let _wait_for_finality_gauge = scopeguard::guard(wait_for_finality_gauge, |in_flight| {
720            in_flight.dec();
721        });
722
723        let ticket = self
724            .submit(
725                quorum_driver,
726                epoch_store.clone(),
727                transaction.clone(),
728                request,
729                client_addr,
730            )
731            .await
732            .map_err(|e| {
733                warn!(?tx_digest, "QuorumDriverInternalError: {e:?}");
734                QuorumDriverError::QuorumDriverInternal(e)
735            })?;
736
737        let Ok(result) = timeout(WAIT_FOR_FINALITY_TIMEOUT, ticket).await else {
738            debug!(?tx_digest, "Timeout waiting for transaction finality.");
739            self.metrics.wait_for_finality_timeout.inc();
740            return Err(QuorumDriverError::TimeoutBeforeFinality);
741        };
742        add_server_timing("wait_for_finality");
743
744        drop(_txn_finality_timer);
745        drop(_wait_for_finality_gauge);
746        self.metrics.wait_for_finality_finished.inc();
747
748        match result {
749            Err(err) => {
750                warn!(?tx_digest, "QuorumDriverInternalError: {err:?}");
751                Err(QuorumDriverError::QuorumDriverInternal(err))
752            }
753            Ok(Err(err)) => Err(err),
754            Ok(Ok(response)) => {
755                good_response_metrics.inc();
756                Ok((transaction, response))
757            }
758        }
759    }
760
761    /// Submits the transaction to Quorum Driver for execution.
762    /// Returns an awaitable Future.
763    #[instrument(name = "tx_orchestrator_submit", level = "trace", skip_all)]
764    async fn submit(
765        &self,
766        quorum_driver: &Arc<QuorumDriverHandler<A>>,
767        epoch_store: Arc<AuthorityPerEpochStore>,
768        transaction: VerifiedTransaction,
769        request: ExecuteTransactionRequestV1,
770        client_addr: Option<SocketAddr>,
771    ) -> IotaResult<impl Future<Output = IotaResult<QuorumDriverResult>> + '_> {
772        let tx_digest = *transaction.digest();
773        let ticket = self.notifier.register_one(&tx_digest);
774        // TODO(william) need to also write client adr to pending tx log below
775        // so that we can re-execute with this client addr if we restart
776        if self
777            .pending_tx_log
778            .write_pending_transaction_maybe(&transaction)
779            .await?
780        {
781            debug!(?tx_digest, "no pending request in flight, submitting.");
782            quorum_driver
783                .submit_transaction_no_ticket(request.clone(), client_addr)
784                .await?;
785        }
786        // It's possible that the transaction effects is already stored in DB at this
787        // point. So we also subscribe to that. If we hear from `effects_await`
788        // first, it means the ticket misses the previous notification, and we
789        // want to ask quorum driver to form a certificate for us again, to
790        // serve this request.
791        let cache_reader = self.validator_state.get_transaction_cache_reader().clone();
792        let qd = quorum_driver.clone();
793        Ok(async move {
794            let digests = [tx_digest];
795            let effects_await = epoch_store
796                .within_alive_epoch(cache_reader.try_notify_read_executed_effects(&digests));
797            // let-and-return necessary to satisfy borrow checker.
798            let res = match select(ticket, effects_await.boxed()).await {
799                Either::Left((quorum_driver_response, _)) => Ok(quorum_driver_response),
800                Either::Right((_, unfinished_quorum_driver_task)) => {
801                    debug!(
802                        ?tx_digest,
803                        "Effects are available in DB, use quorum driver to get a certificate"
804                    );
805                    qd.submit_transaction_no_ticket(request, client_addr)
806                        .await?;
807                    Ok(unfinished_quorum_driver_task.await)
808                }
809            };
810            res
811        })
812    }
813
814    #[instrument(name = "tx_orchestrator_wait_for_finalized_tx_executed_locally_with_timeout", level = "debug", skip_all, fields(tx_digest = ?transaction.digest()), err)]
815    async fn wait_for_finalized_tx_executed_locally_with_timeout(
816        validator_state: &Arc<AuthorityState>,
817        transaction: &VerifiedTransaction,
818        metrics: &TransactionOrchestratorMetrics,
819    ) -> IotaResult {
820        let tx_digest = *transaction.digest();
821        metrics.local_execution_in_flight.inc();
822        let _metrics_guard =
823            scopeguard::guard(metrics.local_execution_in_flight.clone(), |in_flight| {
824                in_flight.dec();
825            });
826
827        let _guard = if transaction.contains_shared_object() {
828            metrics.local_execution_latency_shared_obj.start_timer()
829        } else {
830            metrics.local_execution_latency_single_writer.start_timer()
831        };
832        debug!(
833            ?tx_digest,
834            "Waiting for finalized tx to be executed locally."
835        );
836        match timeout(
837            LOCAL_EXECUTION_TIMEOUT,
838            validator_state
839                .get_transaction_cache_reader()
840                .try_notify_read_executed_effects_digests(&[tx_digest]),
841        )
842        .instrument(trace_span!("local_execution"))
843        .await
844        {
845            Err(_elapsed) => {
846                debug!(
847                    ?tx_digest,
848                    "Waiting for finalized tx to be executed locally timed out within {:?}.",
849                    LOCAL_EXECUTION_TIMEOUT
850                );
851                metrics.local_execution_timeout.inc();
852                Err(IotaError::Timeout)
853            }
854            Ok(Err(err)) => {
855                debug!(
856                    ?tx_digest,
857                    "Waiting for finalized tx to be executed locally failed with error: {:?}", err
858                );
859                metrics.local_execution_failure.inc();
860                Err(IotaError::TransactionOrchestratorLocalExecution {
861                    error: err.to_string(),
862                })
863            }
864            Ok(Ok(_)) => {
865                metrics.local_execution_success.inc();
866                Ok(())
867            }
868        }
869    }
870
871    // TODO: Potentially cleanup this function and pending transaction log.
872    async fn loop_pending_transaction_log(
873        mut effects_receiver: Receiver<QuorumDriverEffectsQueueResult>,
874        pending_transaction_log: Arc<WritePathPendingTransactionLog>,
875    ) {
876        loop {
877            match effects_receiver.recv().await {
878                Ok(Ok((transaction, ..))) => {
879                    let tx_digest = transaction.digest();
880                    if let Err(err) = pending_transaction_log.finish_transaction(tx_digest) {
881                        error!(
882                            ?tx_digest,
883                            "Failed to finish transaction in pending transaction log: {err}"
884                        );
885                    }
886                }
887                Ok(Err((tx_digest, _err))) => {
888                    if let Err(err) = pending_transaction_log.finish_transaction(&tx_digest) {
889                        error!(
890                            ?tx_digest,
891                            "Failed to finish transaction in pending transaction log: {err}"
892                        );
893                    }
894                }
895                Err(RecvError::Closed) => {
896                    error!("Sender of effects subscriber queue has been dropped!");
897                    return;
898                }
899                Err(RecvError::Lagged(skipped_count)) => {
900                    warn!("Skipped {skipped_count} transasctions in effects subscriber queue.");
901                }
902            }
903        }
904    }
905
906    /// Returns the quorum driver, or `None` under the P-COOL flow.
907    pub fn quorum_driver(&self) -> Option<&Arc<QuorumDriverHandler<A>>> {
908        match &self.driver {
909            Driver::Quorum(handler) => Some(handler),
910            Driver::Transaction(_) => None,
911        }
912    }
913
914    /// Returns the quorum driver, or `None` under the P-COOL flow.
915    pub fn clone_quorum_driver(&self) -> Option<Arc<QuorumDriverHandler<A>>> {
916        self.quorum_driver().cloned()
917    }
918
919    /// Returns the transaction driver, or `None` when the P-COOL flow is
920    /// disabled.
921    pub fn transaction_driver(&self) -> Option<&Arc<TransactionDriver<A>>> {
922        match &self.driver {
923            Driver::Quorum(_) => None,
924            Driver::Transaction(td) => Some(td),
925        }
926    }
927
928    /// Returns the authority aggregator of the active driver.
929    pub fn clone_authority_aggregator(&self) -> Arc<AuthorityAggregator<A>> {
930        match &self.driver {
931            Driver::Quorum(qd) => qd.authority_aggregator().load_full(),
932            Driver::Transaction(td) => td.authority_aggregator().load_full(),
933        }
934    }
935
936    /// Returns `None` under the P-COOL flow, which has no effects broadcast.
937    pub fn subscribe_to_effects_queue(&self) -> Option<Receiver<QuorumDriverEffectsQueueResult>> {
938        self.quorum_driver().map(|qd| qd.subscribe_to_effects())
939    }
940
941    fn update_metrics(
942        &'_ self,
943        transaction: &VerifiedTransaction,
944    ) -> (impl Drop, &'_ GenericCounter<AtomicU64>) {
945        let (in_flight, good_response) = if transaction.contains_shared_object() {
946            self.metrics.total_req_received_shared_object.inc();
947            (
948                self.metrics.req_in_flight_shared_object.clone(),
949                &self.metrics.good_response_shared_object,
950            )
951        } else {
952            self.metrics.total_req_received_single_writer.inc();
953            (
954                self.metrics.req_in_flight_single_writer.clone(),
955                &self.metrics.good_response_single_writer,
956            )
957        };
958        in_flight.inc();
959        (
960            scopeguard::guard(in_flight, |in_flight| {
961                in_flight.dec();
962            }),
963            good_response,
964        )
965    }
966
967    fn schedule_txes_in_log(
968        pending_tx_log: Arc<WritePathPendingTransactionLog>,
969        quorum_driver: Arc<QuorumDriverHandler<A>>,
970    ) {
971        spawn_logged_monitored_task!(async move {
972            if std::env::var("SKIP_LOADING_FROM_PENDING_TX_LOG").is_ok() {
973                info!("Skipping loading pending transactions from pending_tx_log.");
974                return;
975            }
976            let pending_txes = pending_tx_log
977                .load_all_pending_transactions()
978                .expect("failed to load all pending transactions");
979            info!(
980                "Recovering {} pending transactions from pending_tx_log.",
981                pending_txes.len()
982            );
983            for (i, tx) in pending_txes.into_iter().enumerate() {
984                // TODO: ideally pending_tx_log would not contain VerifiedTransaction, but that
985                // requires a migration.
986                let tx = tx.into_inner();
987                let tx_digest = *tx.digest();
988                // It's not impossible we fail to enqueue a task but that's not the end of
989                // world. TODO(william) correctly extract client_addr from logs
990                if let Err(err) = quorum_driver
991                    .submit_transaction_no_ticket(
992                        ExecuteTransactionRequestV1 {
993                            transaction: tx,
994                            include_events: true,
995                            include_input_objects: false,
996                            include_output_objects: false,
997                            include_auxiliary_data: false,
998                        },
999                        None,
1000                    )
1001                    .await
1002                {
1003                    warn!(
1004                        ?tx_digest,
1005                        "Failed to enqueue transaction from pending_tx_log, err: {err:?}"
1006                    );
1007                } else {
1008                    debug!(?tx_digest, "Enqueued transaction from pending_tx_log");
1009                    if (i + 1) % 1000 == 0 {
1010                        info!("Enqueued {} transactions from pending_tx_log.", i + 1);
1011                    }
1012                }
1013            }
1014            // Transactions will be cleaned up in
1015            // loop_execute_finalized_tx_locally() after they
1016            // produce effects.
1017        });
1018    }
1019
1020    pub fn load_all_pending_transactions(&self) -> IotaResult<Vec<VerifiedTransaction>> {
1021        self.pending_tx_log.load_all_pending_transactions()
1022    }
1023}
1024
1025/// Convert a `QuorumDriverResponse` (contains
1026/// `VerifiedCertifiedTransactionEffects`) to the V1 response format that uses
1027/// `FinalizedEffects`.
1028fn quorum_driver_response_to_v1(response: QuorumDriverResponse) -> ExecuteTransactionResponseV1 {
1029    let QuorumDriverResponse {
1030        effects_cert,
1031        events,
1032        input_objects,
1033        output_objects,
1034        auxiliary_data,
1035    } = response;
1036    ExecuteTransactionResponseV1 {
1037        effects: FinalizedEffects::new_from_effects_cert(effects_cert.into()),
1038        events,
1039        input_objects,
1040        output_objects,
1041        auxiliary_data,
1042    }
1043}
1044
1045/// Convert a `transaction_driver_types::FinalizedEffects` into a
1046/// `quorum_driver_types::FinalizedEffects`.
1047fn convert_td_to_qd_effects(td: TdFinalizedEffects) -> FinalizedEffects {
1048    let finality_info = match td.finality_info {
1049        TdEffectsFinalityInfo::Certified(sig) => EffectsFinalityInfo::Certified(sig),
1050        TdEffectsFinalityInfo::Checkpointed(epoch, seq) => {
1051            EffectsFinalityInfo::Checkpointed(epoch, seq)
1052        }
1053        TdEffectsFinalityInfo::QuorumExecuted(epoch) => EffectsFinalityInfo::QuorumExecuted(epoch),
1054        TdEffectsFinalityInfo::UncertifiedSingleValidator(epoch) => {
1055            EffectsFinalityInfo::UncertifiedSingleValidator(epoch)
1056        }
1057    };
1058    FinalizedEffects {
1059        effects: td.effects,
1060        finality_info,
1061    }
1062}
1063
1064/// Map a `TransactionDriverError` to a `QuorumDriverError` for client
1065/// reporting. The variant choice signals retriability: clients retry on
1066/// `QuorumDriverInternal`, `FailedWithTransientErrorAfterMaximumAttempts`,
1067/// and `TimeoutBeforeFinality`, but treat `InvalidTransaction` /
1068/// `InvalidUserSignature` as terminal. Submission-time rejections that
1069/// cannot succeed on resubmission must therefore not be reported as
1070/// internal.
1071fn map_td_error_to_qd(e: TransactionDriverError) -> QuorumDriverError {
1072    use TransactionDriverError::*;
1073    match e {
1074        ValidationFailed { error } => {
1075            QuorumDriverError::InvalidUserSignature(IotaError::InvalidSignature { error })
1076        }
1077        TimeoutWithLastRetriableError { .. } => QuorumDriverError::TimeoutBeforeFinality,
1078        RejectedByValidators {
1079            submission_non_retriable_errors,
1080            ..
1081        } => {
1082            // f+1 stake of validators returned non-retriable errors during
1083            // submission (bad signature, malformed tx, lock conflict, ...).
1084            // f+1 means at least one honest validator considered this tx
1085            // invalid, so resubmitting the same bytes cannot succeed.
1086            let representative = submission_non_retriable_errors
1087                .errors
1088                .into_iter()
1089                .next()
1090                .map(|(msg, _, _, _)| msg)
1091                .unwrap_or_else(|| "transaction rejected as invalid during submission".to_string());
1092            QuorumDriverError::InvalidTransaction(IotaError::Unknown(format!(
1093                "Transaction was rejected as invalid by more than 1/3 of validator stake \
1094                 during submission (non-retriable): {representative}"
1095            )))
1096        }
1097        Aborted {
1098            submission_retriable_errors,
1099            submission_non_retriable_errors,
1100            ..
1101        } => {
1102            // Driver exhausted the validator list without reaching the f+1
1103            // non-retriable threshold — most failures were transient
1104            // (validator down, network, overload). Surface as retriable so
1105            // the client can resubmit.
1106            let attempts = count_validator_attempts(&submission_retriable_errors)
1107                + count_validator_attempts(&submission_non_retriable_errors);
1108            QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts {
1109                total_attempts: attempts,
1110            }
1111        }
1112        other @ ForkedExecution { .. } => {
1113            // Validators disagree on effects digests — a protocol-level
1114            // invariant violation, never a client retry case. Log loud so
1115            // on-call sees it; surface as internal.
1116            let msg = other.to_string();
1117            error!("TransactionDriver observed forked execution: {msg}");
1118            QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1119        }
1120        other @ ClientInternal { .. } => {
1121            let msg = other.to_string();
1122            warn!("TransactionDriver client-internal error: {msg}");
1123            QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1124        }
1125        other @ SubmittedButFetchFailed { .. } => {
1126            let msg = other.to_string();
1127            warn!("TransactionDriver submitted transaction but failed to fetch effects: {msg}");
1128            QuorumDriverError::QuorumDriverInternal(IotaError::Unknown(msg))
1129        }
1130    }
1131}
1132
1133fn count_validator_attempts(errors: &AggregatedRequestErrors) -> u32 {
1134    errors
1135        .errors
1136        .iter()
1137        .map(|(_, authorities, _, _)| authorities.len() as u32)
1138        .sum()
1139}
1140
1141/// Prometheus metrics which can be displayed in Grafana, queried and alerted on
1142#[derive(Clone)]
1143pub struct TransactionOrchestratorMetrics {
1144    total_req_received_single_writer: GenericCounter<AtomicU64>,
1145    total_req_received_shared_object: GenericCounter<AtomicU64>,
1146
1147    good_response_single_writer: GenericCounter<AtomicU64>,
1148    good_response_shared_object: GenericCounter<AtomicU64>,
1149
1150    req_in_flight_single_writer: GenericGauge<AtomicI64>,
1151    req_in_flight_shared_object: GenericGauge<AtomicI64>,
1152
1153    wait_for_finality_in_flight: GenericGauge<AtomicI64>,
1154    wait_for_finality_finished: GenericCounter<AtomicU64>,
1155    wait_for_finality_timeout: GenericCounter<AtomicU64>,
1156
1157    local_execution_in_flight: GenericGauge<AtomicI64>,
1158    local_execution_success: GenericCounter<AtomicU64>,
1159    local_execution_timeout: GenericCounter<AtomicU64>,
1160    local_execution_failure: GenericCounter<AtomicU64>,
1161
1162    // Bumped when the skip-effect-certification path reconciles against the
1163    // local cache but the cache has no events for a tx the single submitter
1164    // claimed had events. Uncertified events are rejected and the request
1165    // fails via the safety guard.
1166    skip_effect_cert_events_cache_miss: GenericCounter<AtomicU64>,
1167
1168    // Bumped when local checkpoint inclusion completes before the TD
1169    // skip-effect-certification call returns. Indicates the driver was slow
1170    // (e.g., corroborating a single-validator rejection) and the checkpoint
1171    // race cancelled the in-flight driver work in favor of rebuilding from
1172    // the local cache.
1173    skip_effect_cert_checkpoint_overrode_driver: GenericCounter<AtomicU64>,
1174
1175    request_latency_single_writer: Histogram,
1176    request_latency_shared_obj: Histogram,
1177    wait_for_finality_latency_single_writer: Histogram,
1178    wait_for_finality_latency_shared_obj: Histogram,
1179    local_execution_latency_single_writer: Histogram,
1180    local_execution_latency_shared_obj: Histogram,
1181}
1182
1183// Note that labeled-metrics are stored upfront individually
1184// to mitigate the perf hit by MetricsVec.
1185// See https://github.com/tikv/rust-prometheus/tree/master/static-metric
1186impl TransactionOrchestratorMetrics {
1187    pub fn new(registry: &Registry) -> Self {
1188        let total_req_received = register_int_counter_vec_with_registry!(
1189            "tx_orchestrator_total_req_received",
1190            "Total number of executions request Transaction Orchestrator receives, group by tx type",
1191            &["tx_type"],
1192            registry
1193        )
1194        .unwrap();
1195
1196        let total_req_received_single_writer =
1197            total_req_received.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1198        let total_req_received_shared_object =
1199            total_req_received.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1200
1201        let good_response = register_int_counter_vec_with_registry!(
1202            "tx_orchestrator_good_response",
1203            "Total number of good responses Transaction Orchestrator generates, group by tx type",
1204            &["tx_type"],
1205            registry
1206        )
1207        .unwrap();
1208
1209        let good_response_single_writer =
1210            good_response.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1211        let good_response_shared_object = good_response.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1212
1213        let req_in_flight = register_int_gauge_vec_with_registry!(
1214            "tx_orchestrator_req_in_flight",
1215            "Number of requests in flights Transaction Orchestrator processes, group by tx type",
1216            &["tx_type"],
1217            registry
1218        )
1219        .unwrap();
1220
1221        let req_in_flight_single_writer =
1222            req_in_flight.with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]);
1223        let req_in_flight_shared_object = req_in_flight.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]);
1224
1225        let request_latency = register_histogram_vec_with_registry!(
1226            "tx_orchestrator_request_latency",
1227            "Time spent in processing one Transaction Orchestrator request",
1228            &["tx_type"],
1229            iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1230            registry,
1231        )
1232        .unwrap();
1233        let wait_for_finality_latency = register_histogram_vec_with_registry!(
1234            "tx_orchestrator_wait_for_finality_latency",
1235            "Time spent in waiting for one Transaction Orchestrator request gets finalized",
1236            &["tx_type"],
1237            iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1238            registry,
1239        )
1240        .unwrap();
1241        let local_execution_latency = register_histogram_vec_with_registry!(
1242            "tx_orchestrator_local_execution_latency",
1243            "Time spent in waiting for one Transaction Orchestrator gets locally executed",
1244            &["tx_type"],
1245            iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
1246            registry,
1247        )
1248        .unwrap();
1249
1250        Self {
1251            total_req_received_single_writer,
1252            total_req_received_shared_object,
1253            good_response_single_writer,
1254            good_response_shared_object,
1255            req_in_flight_single_writer,
1256            req_in_flight_shared_object,
1257            wait_for_finality_in_flight: register_int_gauge_with_registry!(
1258                "tx_orchestrator_wait_for_finality_in_flight",
1259                "Number of in flight txns Transaction Orchestrator are waiting for finality for",
1260                registry,
1261            )
1262            .unwrap(),
1263            wait_for_finality_finished: register_int_counter_with_registry!(
1264                "tx_orchestrator_wait_for_finality_finished",
1265                "Total number of txns Transaction Orchestrator gets responses from Quorum Driver before timeout, either success or failure",
1266                registry,
1267            )
1268            .unwrap(),
1269            wait_for_finality_timeout: register_int_counter_with_registry!(
1270                "tx_orchestrator_wait_for_finality_timeout",
1271                "Total number of txns timing out in waiting for finality Transaction Orchestrator handles",
1272                registry,
1273            )
1274            .unwrap(),
1275            local_execution_in_flight: register_int_gauge_with_registry!(
1276                "tx_orchestrator_local_execution_in_flight",
1277                "Number of local execution txns in flights Transaction Orchestrator handles",
1278                registry,
1279            )
1280            .unwrap(),
1281            local_execution_success: register_int_counter_with_registry!(
1282                "tx_orchestrator_local_execution_success",
1283                "Total number of successful local execution txns Transaction Orchestrator handles",
1284                registry,
1285            )
1286            .unwrap(),
1287            local_execution_timeout: register_int_counter_with_registry!(
1288                "tx_orchestrator_local_execution_timeout",
1289                "Total number of timed-out local execution txns Transaction Orchestrator handles",
1290                registry,
1291            )
1292            .unwrap(),
1293            local_execution_failure: register_int_counter_with_registry!(
1294                "tx_orchestrator_local_execution_failure",
1295                "Total number of failed local execution txns Transaction Orchestrator handles",
1296                registry,
1297            )
1298            .unwrap(),
1299            skip_effect_cert_events_cache_miss: register_int_counter_with_registry!(
1300                "tx_orchestrator_skip_effect_cert_events_cache_miss",
1301                "Number of skip-effect-certification responses rejected because the \
1302                 single submitter claimed to have events but the local cache did not \
1303                 corroborate them",
1304                registry,
1305            )
1306            .unwrap(),
1307            skip_effect_cert_checkpoint_overrode_driver: register_int_counter_with_registry!(
1308                "tx_orchestrator_skip_effect_cert_checkpoint_overrode_driver",
1309                "Number of skip-effect-certification requests where local checkpoint \
1310                 inclusion completed before the TransactionDriver call returned; the \
1311                 driver future was cancelled and the response was rebuilt from cache",
1312                registry,
1313            )
1314            .unwrap(),
1315            request_latency_single_writer: request_latency
1316                .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1317            request_latency_shared_obj: request_latency.with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1318            wait_for_finality_latency_single_writer: wait_for_finality_latency
1319                .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1320            wait_for_finality_latency_shared_obj: wait_for_finality_latency
1321                .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1322            local_execution_latency_single_writer: local_execution_latency
1323                .with_label_values(&[TX_TYPE_SINGLE_WRITER_TX]),
1324            local_execution_latency_shared_obj: local_execution_latency
1325                .with_label_values(&[TX_TYPE_SHARED_OBJ_TX]),
1326        }
1327    }
1328
1329    pub fn new_for_tests() -> Self {
1330        let registry = Registry::new();
1331        Self::new(&registry)
1332    }
1333}
1334
1335#[async_trait::async_trait]
1336impl<A> iota_types::transaction_executor::TransactionExecutor for TransactionOrchestrator<A>
1337where
1338    A: AuthorityAPI + Send + Sync + 'static + Clone,
1339{
1340    async fn execute_transaction(
1341        &self,
1342        request: ExecuteTransactionRequestV1,
1343        skip_certification: bool,
1344        client_addr: Option<std::net::SocketAddr>,
1345    ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError> {
1346        self.execute_transaction_v1(request, skip_certification, client_addr)
1347            .await
1348    }
1349
1350    fn simulate_transaction(
1351        &self,
1352        transaction: TransactionData,
1353        checks: VmChecks,
1354    ) -> Result<SimulateTransactionResult, IotaError> {
1355        self.validator_state
1356            .simulate_transaction(transaction, checks)
1357    }
1358
1359    /// Wait for the given transactions to be included in a checkpoint.
1360    ///
1361    /// Returns a mapping from transaction digest to
1362    /// `(checkpoint_sequence_number, checkpoint_timestamp_ms)`.
1363    /// On timeout, returns partial results for any transactions that were
1364    /// already checkpointed.
1365    async fn wait_for_checkpoint_inclusion(
1366        &self,
1367        digests: &[TransactionDigest],
1368        timeout: Duration,
1369    ) -> Result<BTreeMap<TransactionDigest, (CheckpointSequenceNumber, u64)>, IotaError> {
1370        self.validator_state
1371            .wait_for_checkpoint_inclusion(digests, timeout)
1372            .await
1373    }
1374
1375    fn read_transaction_from_cache(
1376        &self,
1377        digest: &TransactionDigest,
1378        include_events: bool,
1379        include_input_objects: bool,
1380        include_output_objects: bool,
1381    ) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1382        read_cached_transaction_data(
1383            &self.validator_state,
1384            digest,
1385            include_events,
1386            include_input_objects,
1387            include_output_objects,
1388        )
1389    }
1390}
1391
1392/// Read a transaction's authoritative data from the local cache. Returns
1393/// `Ok(None)` if the tx hasn't been executed locally yet. Shared by the
1394/// orchestrator's skip-cert response builder and the `TransactionExecutor`
1395/// trait method consumed by the gRPC handler.
1396fn read_cached_transaction_data(
1397    validator_state: &Arc<AuthorityState>,
1398    digest: &TransactionDigest,
1399    include_events: bool,
1400    include_input_objects: bool,
1401    include_output_objects: bool,
1402) -> Result<Option<iota_types::transaction_executor::CachedTransactionData>, IotaError> {
1403    let cache = validator_state.get_transaction_cache_reader();
1404    let Some(effects) = cache.try_get_executed_effects(digest)? else {
1405        return Ok(None);
1406    };
1407
1408    let events = if include_events {
1409        cache.try_get_events(digest)?
1410    } else {
1411        None
1412    };
1413
1414    let input_objects = if include_input_objects {
1415        Some(
1416            validator_state
1417                .get_transaction_input_objects(&effects)
1418                .map_err(|e| IotaError::Unknown(format!("input objects: {e:?}")))?,
1419        )
1420    } else {
1421        None
1422    };
1423    let output_objects = if include_output_objects {
1424        Some(
1425            validator_state
1426                .get_transaction_output_objects(&effects)
1427                .map_err(|e| IotaError::Unknown(format!("output objects: {e:?}")))?,
1428        )
1429    } else {
1430        None
1431    };
1432
1433    Ok(Some(
1434        iota_types::transaction_executor::CachedTransactionData {
1435            effects,
1436            events,
1437            input_objects,
1438            output_objects,
1439        },
1440    ))
1441}