diff --git a/crates/sui-config/src/node.rs b/crates/sui-config/src/node.rs index e885c08cfa9d7..fc4b6dcddfb4f 100644 --- a/crates/sui-config/src/node.rs +++ b/crates/sui-config/src/node.rs @@ -235,6 +235,11 @@ pub struct TransactionDriverConfig { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub allowed_submission_validators: Vec, + /// The list of validators that are blocked from submitting block transactions to (via the transaction driver). + /// Each entry is a validator display name. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blocked_submission_validators: Vec, + /// Enable early transaction validation before submission to consensus. /// This checks for non-retriable errors (like old object versions) and rejects /// transactions early to provide fast feedback to clients. @@ -247,6 +252,7 @@ impl Default for TransactionDriverConfig { fn default() -> Self { Self { allowed_submission_validators: vec![], + blocked_submission_validators: vec![], enable_early_validation: true, } } diff --git a/crates/sui-core/src/transaction_driver/effects_certifier.rs b/crates/sui-core/src/transaction_driver/effects_certifier.rs index c8c5bf4510c01..19e6203b9ae32 100644 --- a/crates/sui-core/src/transaction_driver/effects_certifier.rs +++ b/crates/sui-core/src/transaction_driver/effects_certifier.rs @@ -99,8 +99,13 @@ impl EffectsCertifier { } }; - let mut retrier = - RequestRetrier::new(authority_aggregator, client_monitor, tx_type, vec![]); + let mut retrier = RequestRetrier::new( + authority_aggregator, + client_monitor, + tx_type, + vec![], + vec![], + ); let ping_type = get_ping_type(&tx_digest, tx_type); // Setting this to None at first because if the full effects are already provided, diff --git a/crates/sui-core/src/transaction_driver/mod.rs b/crates/sui-core/src/transaction_driver/mod.rs index 2a26f12752bda..99756f6e82bf4 100644 --- a/crates/sui-core/src/transaction_driver/mod.rs +++ b/crates/sui-core/src/transaction_driver/mod.rs @@ -55,6 +55,10 @@ pub struct SubmitTransactionOptions { /// When submitting a transaction, only the validators in the allowed validator list can be used to submit the transaction to. /// When the allowed validator list is empty, any validator can be used. pub allowed_validators: Vec, + + /// When submitting a transaction, the validators in the blocked validator list cannot be used to submit the transaction to. + /// When the blocked validator list is empty, no restrictions are applied. + pub blocked_validators: Vec, } #[derive(Clone, Debug)] diff --git a/crates/sui-core/src/transaction_driver/request_retrier.rs b/crates/sui-core/src/transaction_driver/request_retrier.rs index a19c54d298cef..c32b3c0d78708 100644 --- a/crates/sui-core/src/transaction_driver/request_retrier.rs +++ b/crates/sui-core/src/transaction_driver/request_retrier.rs @@ -29,6 +29,9 @@ const SELECT_LATENCY_DELTA: f64 = 0.02; /// When an `allowed_validators` is provided, only the validators in the list will be used to submit the transaction to. /// When the allowed validator list is empty, any validator can be used an then the validators are selected based on their scores. /// +/// When a `blocked_validators` is provided, the validators in the list cannot be used to submit the transaction to. +/// When the blocked validator list is empty, no restrictions are applied. +/// /// This component helps to manager this retry pattern. pub(crate) struct RequestRetrier { ranked_clients: VecDeque<(AuthorityName, Arc>)>, @@ -42,6 +45,7 @@ impl RequestRetrier { client_monitor: &Arc>, tx_type: TxType, allowed_validators: Vec, + blocked_validators: Vec, ) -> Self { let ranked_validators = client_monitor.select_shuffled_preferred_validators( &auth_agg.committee, @@ -50,11 +54,14 @@ impl RequestRetrier { ); let ranked_clients = ranked_validators .into_iter() - .filter(|name| { - let display_name = auth_agg.get_display_name(name); - allowed_validators.is_empty() || allowed_validators.contains(&display_name) + .map(|name| (name, auth_agg.get_display_name(&name))) + .filter(|(_name, display_name)| { + allowed_validators.is_empty() || allowed_validators.contains(display_name) + }) + .filter(|(_name, display_name)| { + blocked_validators.is_empty() || !blocked_validators.contains(display_name) }) - .filter_map(|name| { + .filter_map(|(name, _display_name)| { // There is not guarantee that the `name` are in the `auth_agg.authority_clients` if those are coming from the list // of `allowed_validators`, as the provided `auth_agg` might have been updated with a new committee that doesn't contain the validator in question. auth_agg @@ -167,8 +174,13 @@ mod tests { async fn test_next_target() { let auth_agg = Arc::new(get_authority_aggregator(4)); let client_monitor = Arc::new(ValidatorClientMonitor::new_for_test(auth_agg.clone())); - let mut retrier = - RequestRetrier::new(&auth_agg, &client_monitor, TxType::SingleWriter, vec![]); + let mut retrier = RequestRetrier::new( + &auth_agg, + &client_monitor, + TxType::SingleWriter, + vec![], + vec![], + ); for name in auth_agg.committee.names() { retrier.next_target().unwrap(); @@ -215,6 +227,7 @@ mod tests { &client_monitor, TxType::SingleWriter, allowed_validators, + vec![], ); // Should only have 1 remaining client (the known validator) @@ -234,6 +247,7 @@ mod tests { &client_monitor, TxType::SingleWriter, allowed_validators, + vec![], ); // Should have no remaining clients since none of the allowed validators exist @@ -241,6 +255,40 @@ mod tests { } } + #[tokio::test] + async fn test_blocked_validators() { + let auth_agg = Arc::new(get_authority_aggregator(4)); + let client_monitor = Arc::new(ValidatorClientMonitor::new_for_test(auth_agg.clone())); + + // Create a list of validators that should be blocked and never picked up by the retrier. + let blocked_validators = auth_agg + .committee + .names() + .take(3) + .copied() + .collect::>(); + let blocked_display_names = blocked_validators + .iter() + .map(|name| auth_agg.get_display_name(name)) + .collect::>(); + + // Only the last validator will be picked up. + let allowed_validator = auth_agg.committee.names().nth(3).unwrap(); + + let mut retrier = RequestRetrier::new( + &auth_agg, + &client_monitor, + TxType::SingleWriter, + vec![], + blocked_display_names, + ); + + // The last validator will be picked up. + assert_eq!(retrier.next_target().unwrap().0, *allowed_validator); + // No more validators will be picked up. + assert!(retrier.next_target().is_err()); + } + #[tokio::test] async fn test_add_error() { let auth_agg = Arc::new(get_authority_aggregator(4)); @@ -249,8 +297,13 @@ mod tests { // Add retriable errors. { let client_monitor = Arc::new(ValidatorClientMonitor::new_for_test(auth_agg.clone())); - let mut retrier = - RequestRetrier::new(&auth_agg, &client_monitor, TxType::SingleWriter, vec![]); + let mut retrier = RequestRetrier::new( + &auth_agg, + &client_monitor, + TxType::SingleWriter, + vec![], + vec![], + ); // 25% stake. retrier @@ -286,8 +339,13 @@ mod tests { // Add mix of retriable and non-retriable errors. { let client_monitor = Arc::new(ValidatorClientMonitor::new_for_test(auth_agg.clone())); - let mut retrier = - RequestRetrier::new(&auth_agg, &client_monitor, TxType::SingleWriter, vec![]); + let mut retrier = RequestRetrier::new( + &auth_agg, + &client_monitor, + TxType::SingleWriter, + vec![], + vec![], + ); // 25% stake retriable error. retrier diff --git a/crates/sui-core/src/transaction_driver/transaction_submitter.rs b/crates/sui-core/src/transaction_driver/transaction_submitter.rs index 7f5d9e6c86e10..e75d7e824ac9a 100644 --- a/crates/sui-core/src/transaction_driver/transaction_submitter.rs +++ b/crates/sui-core/src/transaction_driver/transaction_submitter.rs @@ -71,6 +71,7 @@ impl TransactionSubmitter { client_monitor, tx_type, options.allowed_validators.clone(), + options.blocked_validators.clone(), ); let ping_label = if request.ping_type.is_some() { diff --git a/crates/sui-core/src/transaction_orchestrator.rs b/crates/sui-core/src/transaction_orchestrator.rs index 2a6593ea050b8..2d90e0c38ed56 100644 --- a/crates/sui-core/src/transaction_orchestrator.rs +++ b/crates/sui-core/src/transaction_orchestrator.rs @@ -79,6 +79,7 @@ pub struct TransactionOrchestrator { transaction_driver: Option>>, td_percentage: u8, td_allowed_submission_list: Vec, + td_blocked_submission_list: Vec, enable_early_validation: bool, } @@ -174,6 +175,19 @@ where .map(|config| config.allowed_submission_validators.clone()) .unwrap_or_default(); + let td_blocked_submission_list = node_config + .transaction_driver_config + .as_ref() + .map(|config| config.blocked_submission_validators.clone()) + .unwrap_or_default(); + + if !td_allowed_submission_list.is_empty() && !td_blocked_submission_list.is_empty() { + panic!( + "Both allowed and blocked submission lists are set, this is not allowed, {:?} {:?}", + td_allowed_submission_list, td_blocked_submission_list + ); + } + let enable_early_validation = node_config .transaction_driver_config .as_ref() @@ -190,6 +204,7 @@ where transaction_driver, td_percentage, td_allowed_submission_list, + td_blocked_submission_list, enable_early_validation, } } @@ -699,6 +714,7 @@ where SubmitTransactionOptions { forwarded_client_addr: client_addr, allowed_validators: self.td_allowed_submission_list.clone(), + blocked_validators: self.td_blocked_submission_list.clone(), }, timeout_duration, )