sui_core/transaction_driver/
mod.rs1mod effects_certifier;
5mod error;
6mod metrics;
7mod reconfig_observer;
8mod request_retrier;
9mod transaction_submitter;
10
11pub use error::TransactionDriverError;
13pub use metrics::*;
14pub use reconfig_observer::{OnsiteReconfigObserver, ReconfigObserver};
15
16use std::{
17 net::SocketAddr,
18 sync::Arc,
19 time::{Duration, Instant},
20};
21
22use arc_swap::ArcSwap;
23use effects_certifier::*;
24use mysten_common::backoff::ExponentialBackoff;
25use mysten_metrics::{monitored_future, spawn_logged_monitored_task};
26use parking_lot::Mutex;
27use rand::Rng;
28use sui_config::NodeConfig;
29use sui_types::{
30 base_types::AuthorityName,
31 committee::EpochId,
32 error::{ErrorCategory, UserInputError},
33 messages_grpc::{SubmitTxRequest, SubmitTxResult, TxType},
34 transaction::TransactionDataAPI as _,
35};
36use tokio::{
37 task::JoinSet,
38 time::{interval, sleep},
39};
40use tracing::instrument;
41use transaction_submitter::*;
42
43use crate::{
44 authority_aggregator::AuthorityAggregator,
45 authority_client::AuthorityAPI,
46 validator_client_monitor::{
47 OperationFeedback, OperationType, ValidatorClientMetrics, ValidatorClientMonitor,
48 },
49};
50
51pub trait AuthorityAggregatorUpdatable<A: Clone>: Send + Sync + 'static {
54 fn epoch(&self) -> EpochId;
55 fn authority_aggregator(&self) -> Arc<AuthorityAggregator<A>>;
56 fn update_authority_aggregator(&self, new_authorities: Arc<AuthorityAggregator<A>>);
57}
58
59#[derive(Clone, Default, Debug)]
61pub struct SubmitTransactionOptions {
62 pub forwarded_client_addr: Option<SocketAddr>,
65
66 pub allowed_validators: Vec<String>,
69
70 pub blocked_validators: Vec<String>,
73}
74
75#[derive(Clone, Debug)]
76pub struct QuorumTransactionResponse {
77 pub effects: sui_types::transaction_driver_types::FinalizedEffects,
78
79 pub events: Option<sui_types::effects::TransactionEvents>,
80 pub input_objects: Option<Vec<sui_types::object::Object>>,
82 pub output_objects: Option<Vec<sui_types::object::Object>>,
84 pub auxiliary_data: Option<Vec<u8>>,
85}
86
87pub struct TransactionDriver<A: Clone> {
88 authority_aggregator: Arc<ArcSwap<AuthorityAggregator<A>>>,
89 state: Mutex<State>,
90 metrics: Arc<TransactionDriverMetrics>,
91 submitter: TransactionSubmitter,
92 certifier: EffectsCertifier,
93 client_monitor: Arc<ValidatorClientMonitor<A>>,
94}
95
96impl<A> TransactionDriver<A>
97where
98 A: AuthorityAPI + Send + Sync + 'static + Clone,
99{
100 pub fn new(
102 authority_aggregator: Arc<AuthorityAggregator<A>>,
103 reconfig_observer: Arc<dyn ReconfigObserver<A> + Sync + Send>,
104 metrics: Arc<TransactionDriverMetrics>,
105 node_config: Option<&NodeConfig>,
106 client_metrics: Arc<ValidatorClientMetrics>,
107 ) -> Arc<Self> {
108 if std::env::var("TRANSACTION_DRIVER").is_ok() {
109 tracing::warn!(
110 "Transaction Driver is the only supported driver for transaction submission. Setting TRANSACTION_DRIVER is a no-op."
111 );
112 }
113
114 let shared_swap = Arc::new(ArcSwap::new(authority_aggregator));
115
116 let monitor_config = node_config
118 .and_then(|nc| nc.validator_client_monitor_config.clone())
119 .unwrap_or_default();
120 let client_monitor =
121 ValidatorClientMonitor::new(monitor_config, client_metrics, shared_swap.clone());
122
123 let driver = Arc::new(Self {
124 authority_aggregator: shared_swap,
125 state: Mutex::new(State::new()),
126 metrics: metrics.clone(),
127 submitter: TransactionSubmitter::new(metrics.clone()),
128 certifier: EffectsCertifier::new(metrics),
129 client_monitor,
130 });
131
132 let driver_clone = driver.clone();
133
134 spawn_logged_monitored_task!(Self::run_latency_checks(driver_clone));
135
136 driver.enable_reconfig(reconfig_observer);
137 driver
138 }
139
140 pub fn authority_aggregator(&self) -> &Arc<ArcSwap<AuthorityAggregator<A>>> {
142 &self.authority_aggregator
143 }
144
145 pub fn select_preferred_validators(&self, delta: f64) -> Vec<AuthorityName> {
146 let authority_aggregator = self.authority_aggregator.load();
147 self.client_monitor
148 .select_shuffled_preferred_validators(&authority_aggregator.committee, delta)
149 }
150
151 #[instrument(level = "error", skip_all, fields(tx_digest = ?request.transaction.as_ref().map(|t| t.digest()), ping = %request.ping_type.is_some()))]
158 pub async fn drive_transaction(
159 &self,
160 request: SubmitTxRequest,
161 options: SubmitTransactionOptions,
162 timeout_duration: Option<Duration>,
163 ) -> Result<QuorumTransactionResponse, TransactionDriverError> {
164 const MAX_DRIVE_TRANSACTION_RETRY_DELAY: Duration = Duration::from_secs(10);
165
166 let tx_data = request.transaction.as_ref().map(|t| t.transaction_data());
167 let amplification_factor =
169 if request.ping_type.is_some() || tx_data.is_some_and(|d| d.is_gasless_transaction()) {
170 1
171 } else {
172 let tx_data = tx_data.unwrap();
173 let gas_price = tx_data.gas_price();
174 let reference_gas_price = self.authority_aggregator.load().reference_gas_price;
175 let amplification_factor = gas_price / reference_gas_price.max(1);
176 if amplification_factor == 0 {
177 return Err(TransactionDriverError::ValidationFailed {
178 error: UserInputError::GasPriceUnderRGP {
179 gas_price,
180 reference_gas_price,
181 }
182 .to_string(),
183 });
184 }
185 amplification_factor
186 };
187
188 let tx_type = request.tx_type();
189 let ping_label = if request.ping_type.is_some() {
190 "true"
191 } else {
192 "false"
193 };
194 let timer = Instant::now();
195
196 self.metrics
197 .total_transactions_submitted
198 .with_label_values(&[tx_type.as_str(), ping_label])
199 .inc();
200
201 let mut backoff = ExponentialBackoff::new(
202 Duration::from_millis(100),
203 MAX_DRIVE_TRANSACTION_RETRY_DELAY,
204 );
205 let mut attempts = 0;
206 let mut latest_retriable_error = None;
207
208 let retry_loop = async {
209 loop {
210 match self
212 .drive_transaction_once(amplification_factor, request.clone(), &options)
213 .await
214 {
215 Ok(resp) => {
216 let settlement_finality_latency = timer.elapsed().as_secs_f64();
217 self.metrics
218 .settlement_finality_latency
219 .with_label_values(&[tx_type.as_str(), ping_label])
220 .observe(settlement_finality_latency);
221 let is_out_of_expected_range = settlement_finality_latency >= 8.0
222 || settlement_finality_latency <= 0.1;
223 tracing::debug!(
224 ?tx_type,
225 ?is_out_of_expected_range,
226 "Settlement finality latency: {:.3} seconds",
227 settlement_finality_latency
228 );
229 self.metrics
231 .transaction_retries
232 .with_label_values(&["success", tx_type.as_str(), ping_label])
233 .observe(attempts as f64);
234 return Ok(resp);
235 }
236 Err(e) => {
237 self.metrics
238 .drive_transaction_errors
239 .with_label_values(&[
240 e.categorize().into(),
241 tx_type.as_str(),
242 ping_label,
243 ])
244 .inc();
245 if !e.is_submission_retriable() {
246 self.metrics
248 .transaction_retries
249 .with_label_values(&["failure", tx_type.as_str(), ping_label])
250 .observe(attempts as f64);
251 if request.transaction.is_some() {
252 tracing::info!(
253 "User transaction failed to finalize (attempt {}), with non-retriable error: {} ({})",
254 attempts,
255 e,
256 Into::<&str>::into(e.categorize())
257 );
258 }
259 return Err(e);
260 }
261 if request.transaction.is_some() {
262 tracing::info!(
263 "User transaction failed to finalize (attempt {}): {} ({}). Retrying ...",
264 attempts,
265 e,
266 Into::<&str>::into(e.categorize())
267 );
268 }
269 latest_retriable_error = Some(e);
271 }
272 }
273
274 let overload = if let Some(e) = &latest_retriable_error {
275 e.categorize() == ErrorCategory::ValidatorOverloaded
276 } else {
277 false
278 };
279 let delay = if overload {
280 const OVERLOAD_ADDITIONAL_DELAY: Duration = Duration::from_secs(10);
282 backoff.next().unwrap() + OVERLOAD_ADDITIONAL_DELAY
283 } else {
284 backoff.next().unwrap()
285 };
286
287 tracing::debug!("Retrying after {:.3}s", delay.as_secs_f32());
288 sleep(delay).await;
289
290 attempts += 1;
291 }
292 };
293
294 match timeout_duration {
295 Some(duration) => {
296 tokio::time::timeout(duration, retry_loop)
297 .await
298 .unwrap_or_else(|_| {
299 let e = TransactionDriverError::TimeoutWithLastRetriableError {
301 last_error: latest_retriable_error.map(Box::new),
302 attempts,
303 timeout: duration,
304 };
305 if request.transaction.is_some() {
306 tracing::info!(
307 "User transaction timed out after {} attempts. Last error: {}",
308 attempts,
309 e
310 );
311 }
312 Err(e)
313 })
314 }
315 None => retry_loop.await,
316 }
317 }
318
319 #[instrument(level = "error", skip_all, err(level = "debug"))]
320 async fn drive_transaction_once(
321 &self,
322 amplification_factor: u64,
323 request: SubmitTxRequest,
324 options: &SubmitTransactionOptions,
325 ) -> Result<QuorumTransactionResponse, TransactionDriverError> {
326 let auth_agg = self.authority_aggregator.load();
327 let start_time = Instant::now();
328 let tx_type = request.tx_type();
329 let tx_digest = request.tx_digest();
330 let ping_type = request.ping_type;
331
332 let (name, submit_txn_result) = self
333 .submitter
334 .submit_transaction(
335 &auth_agg,
336 &self.client_monitor,
337 tx_type,
338 amplification_factor,
339 request,
340 options,
341 )
342 .await?;
343 if let SubmitTxResult::Rejected { error } = &submit_txn_result {
344 return Err(TransactionDriverError::ClientInternal {
345 error: format!(
346 "SubmitTxResult::Rejected should have been returned as an error in submit_transaction(): {}",
347 error
348 ),
349 });
350 }
351
352 let result = self
354 .certifier
355 .get_certified_finalized_effects(
356 &auth_agg,
357 &self.client_monitor,
358 tx_digest,
359 tx_type,
360 name,
361 submit_txn_result,
362 options,
363 )
364 .await;
365
366 if result.is_ok() {
367 self.client_monitor
368 .record_interaction_result(OperationFeedback {
369 authority_name: name,
370 display_name: auth_agg.get_display_name(&name),
371 operation: if tx_type == TxType::SingleWriter {
372 OperationType::SingleWriterFinality
373 } else {
374 OperationType::SharedObjectFinality
375 },
376 ping_type,
377 result: Ok(start_time.elapsed()),
378 });
379 }
380 result
381 }
382
383 async fn run_latency_checks(self: Arc<Self>) {
385 const INTERVAL_BETWEEN_RUNS: Duration = Duration::from_secs(15);
386 const MAX_JITTER: Duration = Duration::from_secs(10);
387 const PING_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
388
389 let mut interval = interval(INTERVAL_BETWEEN_RUNS);
390 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
391
392 loop {
393 interval.tick().await;
394
395 let auth_agg = self.authority_aggregator.load().clone();
398 let validators = auth_agg.committee.names().cloned().collect::<Vec<_>>();
399
400 self.metrics.latency_check_runs.inc();
401
402 let mut tasks = JoinSet::new();
403
404 for name in validators {
405 let display_name = auth_agg.get_display_name(&name);
406 let delay_ms = rand::thread_rng().gen_range(0..MAX_JITTER.as_millis()) as u64;
407 let self_clone = self.clone();
408
409 let task = async move {
410 if delay_ms > 0 {
412 tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
413 }
414 let start_time = Instant::now();
415
416 match self_clone
418 .drive_transaction(
419 SubmitTxRequest::new_ping(),
420 SubmitTransactionOptions {
421 allowed_validators: vec![display_name.clone()],
422 ..Default::default()
423 },
424 Some(PING_REQUEST_TIMEOUT),
425 )
426 .await
427 {
428 Ok(_) => {
429 tracing::debug!(
430 "Ping transaction to validator {} completed end to end in {} seconds",
431 display_name,
432 start_time.elapsed().as_secs_f64()
433 );
434 }
435 Err(err) => {
436 tracing::debug!(
437 "Failed to get certified finalized effects for ping transaction to validator {}: {}",
438 display_name,
439 err
440 );
441 }
442 }
443 };
444
445 tasks.spawn(task);
446 }
447
448 while let Some(result) = tasks.join_next().await {
449 if let Err(e) = result {
450 tracing::debug!("Error while driving ping transaction: {}", e);
451 }
452 }
453 }
454 }
455
456 fn enable_reconfig(
457 self: &Arc<Self>,
458 reconfig_observer: Arc<dyn ReconfigObserver<A> + Sync + Send>,
459 ) {
460 let driver = self.clone();
461 self.state.lock().tasks.spawn(monitored_future!(async move {
462 let mut reconfig_observer = reconfig_observer.clone_boxed();
463 reconfig_observer.run(driver).await;
464 }));
465 }
466}
467
468impl<A> AuthorityAggregatorUpdatable<A> for TransactionDriver<A>
469where
470 A: AuthorityAPI + Send + Sync + 'static + Clone,
471{
472 fn epoch(&self) -> EpochId {
473 self.authority_aggregator.load().committee.epoch
474 }
475
476 fn authority_aggregator(&self) -> Arc<AuthorityAggregator<A>> {
477 self.authority_aggregator.load_full()
478 }
479
480 fn update_authority_aggregator(&self, new_authorities: Arc<AuthorityAggregator<A>>) {
481 tracing::info!(
482 "Transaction Driver updating AuthorityAggregator with committee {}",
483 new_authorities.committee
484 );
485
486 self.authority_aggregator.store(new_authorities);
487 }
488}
489
490struct State {
492 tasks: JoinSet<()>,
493}
494
495impl State {
496 fn new() -> Self {
497 Self {
498 tasks: JoinSet::new(),
499 }
500 }
501}