1use super::execution_time_estimator::ExecutionTimeEstimator;
5use crate::authority::transaction_deferral::DeferralKey;
6use crate::consensus_handler::{ConsensusCommitInfo, IndirectStateObserver};
7use mysten_common::fatal;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig};
11use sui_types::base_types::{ObjectID, TransactionDigest};
12use sui_types::executable_transaction::VerifiedExecutableTransaction;
13use sui_types::messages_consensus::Round;
14use sui_types::transaction::{Argument, SharedInputObject, TransactionDataAPI};
15use tracing::{debug, trace};
16
17#[derive(PartialEq, Eq, Clone, Debug)]
18struct Params {
19 mode: PerObjectCongestionControlMode,
20 for_randomness: bool,
21 max_accumulated_txn_cost_per_object_in_commit: u64,
22 gas_budget_based_txn_cost_cap_factor: Option<u64>,
23 gas_budget_based_txn_cost_absolute_cap: Option<u64>,
24 max_txn_cost_overage_per_object_in_commit: u64,
25 allowed_txn_cost_overage_burst_per_object_in_commit: u64,
26}
27
28impl Params {
29 pub fn commit_budget(&self, commit_info: &ConsensusCommitInfo) -> u64 {
32 match self.mode {
33 PerObjectCongestionControlMode::ExecutionTimeEstimate(params) => {
34 let estimated_commit_period = commit_info.estimated_commit_period();
35 let commit_period_micros = estimated_commit_period.as_micros() as u64;
36 let mut budget =
37 commit_period_micros.saturating_mul(params.target_utilization) / 100;
38 if self.for_randomness {
39 budget = budget.saturating_mul(params.randomness_scalar) / 100;
40 }
41 budget
42 }
43 _ => self.max_accumulated_txn_cost_per_object_in_commit,
44 }
45 }
46
47 pub fn max_burst(&self) -> u64 {
50 match self.mode {
51 PerObjectCongestionControlMode::ExecutionTimeEstimate(params) => {
52 let mut burst = params.allowed_txn_cost_overage_burst_limit_us;
53 if self.for_randomness {
54 burst = burst.saturating_mul(params.randomness_scalar) / 100;
55 }
56 burst
57 }
58 _ => self.allowed_txn_cost_overage_burst_per_object_in_commit,
59 }
60 }
61
62 pub fn max_overage(&self) -> u64 {
66 match self.mode {
67 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => u64::MAX,
68 _ => self.max_txn_cost_overage_per_object_in_commit,
69 }
70 }
71
72 pub fn gas_budget_based_txn_cost_cap_factor(&self) -> u64 {
73 match self.mode {
74 PerObjectCongestionControlMode::TotalGasBudgetWithCap => self
75 .gas_budget_based_txn_cost_cap_factor
76 .expect("cap factor must be set if TotalGasBudgetWithCap mode is used."),
77 _ => fatal!(
78 "gas_budget_based_txn_cost_cap_factor is only used in TotalGasBudgetWithCap mode."
79 ),
80 }
81 }
82
83 pub fn gas_budget_based_txn_cost_absolute_cap(&self) -> Option<u64> {
84 match self.mode {
85 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
86 self.gas_budget_based_txn_cost_absolute_cap
87 }
88 _ => fatal!(
89 "gas_budget_based_txn_cost_absolute_cap is only used in TotalGasBudgetWithCap mode."
90 ),
91 }
92 }
93}
94
95#[derive(PartialEq, Eq, Clone, Debug)]
108pub struct SharedObjectCongestionTracker {
109 object_execution_cost: HashMap<ObjectID, u64>,
110 params: Params,
111}
112
113impl SharedObjectCongestionTracker {
114 pub fn new(
115 initial_object_debts: impl IntoIterator<Item = (ObjectID, u64)>,
116 mode: PerObjectCongestionControlMode,
117 for_randomness: bool,
118 max_accumulated_txn_cost_per_object_in_commit: Option<u64>,
119 gas_budget_based_txn_cost_cap_factor: Option<u64>,
120 gas_budget_based_txn_cost_absolute_cap_commit_count: Option<u64>,
121 max_txn_cost_overage_per_object_in_commit: u64,
122 allowed_txn_cost_overage_burst_per_object_in_commit: u64,
123 ) -> Self {
124 assert!(
125 allowed_txn_cost_overage_burst_per_object_in_commit
126 <= max_txn_cost_overage_per_object_in_commit,
127 "burst limit must be <= absolute limit; allowed_txn_cost_overage_burst_per_object_in_commit = {allowed_txn_cost_overage_burst_per_object_in_commit}, max_txn_cost_overage_per_object_in_commit = {max_txn_cost_overage_per_object_in_commit}"
128 );
129
130 let object_execution_cost: HashMap<ObjectID, u64> =
131 initial_object_debts.into_iter().collect();
132 let max_accumulated_txn_cost_per_object_in_commit =
133 if mode == PerObjectCongestionControlMode::None {
134 0
135 } else {
136 max_accumulated_txn_cost_per_object_in_commit.expect(
137 "max_accumulated_txn_cost_per_object_in_commit must be set if mode is not None",
138 )
139 };
140 let gas_budget_based_txn_cost_absolute_cap =
141 gas_budget_based_txn_cost_absolute_cap_commit_count
142 .map(|m| m * max_accumulated_txn_cost_per_object_in_commit);
143 trace!(
144 "created SharedObjectCongestionTracker with
145 {} initial object debts,
146 mode: {mode:?},
147 max_accumulated_txn_cost_per_object_in_commit: {max_accumulated_txn_cost_per_object_in_commit:?},
148 gas_budget_based_txn_cost_cap_factor: {gas_budget_based_txn_cost_cap_factor:?},
149 gas_budget_based_txn_cost_absolute_cap: {gas_budget_based_txn_cost_absolute_cap:?},
150 max_txn_cost_overage_per_object_in_commit: {max_txn_cost_overage_per_object_in_commit:?}",
151 object_execution_cost.len(),
152 );
153 Self {
154 object_execution_cost,
155 params: Params {
156 mode,
157 for_randomness,
158 max_accumulated_txn_cost_per_object_in_commit,
159 gas_budget_based_txn_cost_cap_factor,
160 gas_budget_based_txn_cost_absolute_cap,
161 max_txn_cost_overage_per_object_in_commit,
162 allowed_txn_cost_overage_burst_per_object_in_commit,
163 },
164 }
165 }
166
167 pub fn from_protocol_config(
168 initial_object_debts: impl IntoIterator<Item = (ObjectID, u64)>,
169 protocol_config: &ProtocolConfig,
170 for_randomness: bool,
171 ) -> Self {
172 let max_accumulated_txn_cost_per_object_in_commit =
173 protocol_config.max_accumulated_txn_cost_per_object_in_mysticeti_commit_as_option();
174 Self::new(
175 initial_object_debts,
176 protocol_config.per_object_congestion_control_mode(),
177 for_randomness,
178 if for_randomness {
179 protocol_config
180 .max_accumulated_randomness_txn_cost_per_object_in_mysticeti_commit_as_option()
181 .or(max_accumulated_txn_cost_per_object_in_commit)
182 } else {
183 max_accumulated_txn_cost_per_object_in_commit
184 },
185 protocol_config.gas_budget_based_txn_cost_cap_factor_as_option(),
186 protocol_config.gas_budget_based_txn_cost_absolute_cap_commit_count_as_option(),
187 protocol_config
188 .max_txn_cost_overage_per_object_in_commit_as_option()
189 .unwrap_or(0),
190 protocol_config
191 .allowed_txn_cost_overage_burst_per_object_in_commit_as_option()
192 .unwrap_or(0),
193 )
194 }
195
196 pub fn compute_tx_start_at_cost(&self, shared_input_objects: &[SharedInputObject]) -> u64 {
202 shared_input_objects
203 .iter()
204 .map(|obj| *self.object_execution_cost.get(&obj.id).unwrap_or(&0))
205 .max()
206 .expect("There must be at least one object in shared_input_objects.")
207 }
208
209 pub fn get_tx_cost(
210 &self,
211 execution_time_estimator: Option<&ExecutionTimeEstimator>,
212 cert: &VerifiedExecutableTransaction,
213 indirect_state_observer: &mut IndirectStateObserver,
214 ) -> Option<u64> {
215 let tx_cost = match &self.params.mode {
216 PerObjectCongestionControlMode::None => None,
217 PerObjectCongestionControlMode::TotalGasBudget => Some(cert.gas_budget()),
218 PerObjectCongestionControlMode::TotalTxCount => Some(1),
219 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
220 Some(std::cmp::min(cert.gas_budget(), self.get_tx_cost_cap(cert)))
221 }
222 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
223 let estimate_us = execution_time_estimator
224 .expect("`execution_time_estimator` must be set for PerObjectCongestionControlMode::ExecutionTimeEstimate")
225 .get_estimate(cert.transaction_data())
226 .as_micros()
227 .try_into()
228 .unwrap_or(u64::MAX);
229 if estimate_us >= 15_000 {
230 let digest = cert.digest();
231 debug!(
232 ?digest,
233 "expensive tx cost estimate detected: {estimate_us}us"
234 );
235 }
236
237 Some(estimate_us)
238 }
239 };
240 indirect_state_observer.observe_indirect_state(&tx_cost);
241 tx_cost
242 }
243
244 pub fn should_defer_due_to_object_congestion(
246 &self,
247 tx_cost: Option<u64>,
248 cert: &VerifiedExecutableTransaction,
249 previously_deferred_tx_digests: &HashMap<TransactionDigest, DeferralKey>,
250 commit_info: &ConsensusCommitInfo,
251 ) -> Option<(DeferralKey, Vec<ObjectID>)> {
252 let commit_round = commit_info.round;
253
254 let tx_cost = tx_cost?;
255
256 let shared_input_objects: Vec<_> = cert.shared_input_objects().collect();
257 if shared_input_objects.is_empty() {
258 return None;
260 }
261 let start_cost = self.compute_tx_start_at_cost(&shared_input_objects);
262 let end_cost = start_cost.saturating_add(tx_cost);
263
264 let budget = self.params.commit_budget(commit_info);
265
266 let burst_limit = budget.saturating_add(self.params.max_burst());
268 let absolute_limit = budget.saturating_add(self.params.max_overage());
269
270 if start_cost <= burst_limit && end_cost <= absolute_limit {
271 return None;
272 }
273
274 let mut congested_objects = vec![];
282 for obj in shared_input_objects {
283 if &start_cost == self.object_execution_cost.get(&obj.id).unwrap_or(&0) {
289 congested_objects.push(obj.id);
290 }
291 }
292
293 assert!(!congested_objects.is_empty());
294
295 let deferral_key =
296 if let Some(previous_key) = previously_deferred_tx_digests.get(cert.digest()) {
297 DeferralKey::new_for_consensus_round(
299 commit_round + 1,
300 previous_key.deferred_from_round(),
301 )
302 } else {
303 DeferralKey::new_for_consensus_round(commit_round + 1, commit_round)
306 };
307 Some((deferral_key, congested_objects))
308 }
309
310 pub fn bump_object_execution_cost(
313 &mut self,
314 tx_cost: Option<u64>,
315 cert: &VerifiedExecutableTransaction,
316 ) {
317 let shared_input_objects: Vec<_> = cert.shared_input_objects().collect();
318 if shared_input_objects.is_empty() {
319 return;
320 }
321
322 let Some(tx_cost) = tx_cost else {
323 return;
324 };
325
326 let start_cost = self.compute_tx_start_at_cost(&shared_input_objects);
327 let end_cost = start_cost.saturating_add(tx_cost);
328
329 for obj in shared_input_objects {
330 if obj.is_accessed_exclusively() {
331 let old_end_cost = self.object_execution_cost.insert(obj.id, end_cost);
332 assert!(old_end_cost.is_none() || old_end_cost.unwrap() <= end_cost);
333 }
334 }
335 }
336
337 pub fn accumulated_debts(self, commit_info: &ConsensusCommitInfo) -> Vec<(ObjectID, u64)> {
341 if self.params.max_overage() == 0 {
342 return vec![]; }
344
345 self.object_execution_cost
346 .into_iter()
347 .filter_map(|(obj_id, cost)| {
348 let remaining_cost = cost.saturating_sub(self.params.commit_budget(commit_info));
349 if remaining_cost > 0 {
350 Some((obj_id, remaining_cost))
351 } else {
352 None
353 }
354 })
355 .collect()
356 }
357
358 pub fn max_cost(&self) -> u64 {
360 self.object_execution_cost
361 .values()
362 .max()
363 .copied()
364 .unwrap_or(0)
365 }
366
367 fn get_tx_cost_cap(&self, cert: &VerifiedExecutableTransaction) -> u64 {
368 let mut number_of_move_call = 0;
369 let mut number_of_move_input = 0;
370 for command in cert.transaction_data().kind().iter_commands() {
371 if let sui_types::transaction::Command::MoveCall(move_call) = command {
372 number_of_move_call += 1;
373 for aug in move_call.arguments.iter() {
374 if let Argument::Input(_) = aug {
375 number_of_move_input += 1;
376 }
377 }
378 }
379 }
380 let cap = (number_of_move_call + number_of_move_input) as u64
381 * self.params.gas_budget_based_txn_cost_cap_factor();
382
383 std::cmp::min(
385 cap,
386 self.params
387 .gas_budget_based_txn_cost_absolute_cap()
388 .unwrap_or(u64::MAX),
389 )
390 }
391}
392
393#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
394pub enum CongestionPerObjectDebt {
395 V1(Round, u64),
396}
397
398impl CongestionPerObjectDebt {
399 pub fn new(round: Round, debt: u64) -> Self {
400 Self::V1(round, debt)
401 }
402
403 pub fn into_v1(self) -> (Round, u64) {
404 match self {
405 Self::V1(round, debt) => (round, debt),
406 }
407 }
408}
409
410#[cfg(test)]
411mod object_cost_tests {
412 use super::*;
413
414 use rstest::rstest;
415 use std::time::Duration;
416 use sui_protocol_config::ExecutionTimeEstimateParams;
417 use sui_test_transaction_builder::TestTransactionBuilder;
418 use sui_types::Identifier;
419 use sui_types::base_types::{SequenceNumber, random_object_ref};
420 use sui_types::crypto::{AccountKeyPair, get_key_pair};
421 use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
422 use sui_types::transaction::{CallArg, ObjectArg, SharedObjectMutability, VerifiedTransaction};
423
424 fn construct_shared_input_objects(objects: &[(ObjectID, bool)]) -> Vec<SharedInputObject> {
425 objects
426 .iter()
427 .map(|(id, mutable)| SharedInputObject {
428 id: *id,
429 initial_shared_version: SequenceNumber::new(),
430 mutability: if *mutable {
431 SharedObjectMutability::Mutable
432 } else {
433 SharedObjectMutability::Immutable
434 },
435 })
436 .collect()
437 }
438
439 #[test]
440 fn test_compute_tx_start_at_cost() {
441 let object_id_0 = ObjectID::random();
442 let object_id_1 = ObjectID::random();
443 let object_id_2 = ObjectID::random();
444
445 let shared_object_congestion_tracker = SharedObjectCongestionTracker::new(
446 [(object_id_0, 5), (object_id_1, 10)],
447 PerObjectCongestionControlMode::TotalGasBudget,
448 false,
449 Some(0), None,
451 None,
452 0,
453 0,
454 );
455
456 let shared_input_objects = construct_shared_input_objects(&[(object_id_0, false)]);
457 assert_eq!(
458 shared_object_congestion_tracker.compute_tx_start_at_cost(&shared_input_objects),
459 5
460 );
461
462 let shared_input_objects = construct_shared_input_objects(&[(object_id_1, true)]);
463 assert_eq!(
464 shared_object_congestion_tracker.compute_tx_start_at_cost(&shared_input_objects),
465 10
466 );
467
468 let shared_input_objects =
469 construct_shared_input_objects(&[(object_id_0, false), (object_id_1, false)]);
470 assert_eq!(
471 shared_object_congestion_tracker.compute_tx_start_at_cost(&shared_input_objects),
472 10
473 );
474
475 let shared_input_objects =
476 construct_shared_input_objects(&[(object_id_0, true), (object_id_1, true)]);
477 assert_eq!(
478 shared_object_congestion_tracker.compute_tx_start_at_cost(&shared_input_objects),
479 10
480 );
481
482 let shared_input_objects = construct_shared_input_objects(&[(object_id_2, true)]);
484 assert_eq!(
485 shared_object_congestion_tracker.compute_tx_start_at_cost(&shared_input_objects),
486 0
487 );
488 }
489
490 fn build_transaction(
494 objects: &[(ObjectID, bool)],
495 gas_budget: u64,
496 ) -> VerifiedExecutableTransaction {
497 let (sender, keypair): (_, AccountKeyPair) = get_key_pair();
498 let gas_object = random_object_ref();
499 VerifiedExecutableTransaction::new_system(
500 VerifiedTransaction::new_unchecked(
501 TestTransactionBuilder::new(sender, gas_object, 1000)
502 .with_gas_budget(gas_budget)
503 .move_call(
504 ObjectID::random(),
505 "unimportant_module",
506 "unimportant_function",
507 objects
508 .iter()
509 .map(|(id, mutable)| {
510 CallArg::Object(ObjectArg::SharedObject {
511 id: *id,
512 initial_shared_version: SequenceNumber::new(),
513 mutability: if *mutable {
514 SharedObjectMutability::Mutable
515 } else {
516 SharedObjectMutability::Immutable
517 },
518 })
519 })
520 .collect(),
521 )
522 .build_and_sign(&keypair),
523 ),
524 0,
525 )
526 }
527
528 fn build_programmable_transaction(
529 objects: &[(ObjectID, bool)],
530 number_of_commands: u64,
531 gas_budget: u64,
532 ) -> VerifiedExecutableTransaction {
533 let (sender, keypair): (_, AccountKeyPair) = get_key_pair();
534 let gas_object = random_object_ref();
535
536 let package_id = ObjectID::random();
537 let mut pt_builder = ProgrammableTransactionBuilder::new();
538 let mut arguments = Vec::new();
539 for object in objects {
540 arguments.push(
541 pt_builder
542 .obj(ObjectArg::SharedObject {
543 id: object.0,
544 initial_shared_version: SequenceNumber::new(),
545 mutability: if object.1 {
546 SharedObjectMutability::Mutable
547 } else {
548 SharedObjectMutability::Immutable
549 },
550 })
551 .unwrap(),
552 );
553 }
554 for _ in 0..number_of_commands {
555 pt_builder.programmable_move_call(
556 package_id,
557 Identifier::new("unimportant_module").unwrap(),
558 Identifier::new("unimportant_function").unwrap(),
559 vec![],
560 arguments.clone(),
561 );
562 }
563
564 let pt = pt_builder.finish();
565 VerifiedExecutableTransaction::new_system(
566 VerifiedTransaction::new_unchecked(
567 TestTransactionBuilder::new(sender, gas_object, 1000)
568 .with_gas_budget(gas_budget)
569 .programmable(pt)
570 .build_and_sign(&keypair),
571 ),
572 0,
573 )
574 }
575
576 #[rstest]
577 fn test_should_defer_return_correct_congested_objects(
578 #[values(
579 PerObjectCongestionControlMode::TotalGasBudget,
580 PerObjectCongestionControlMode::TotalTxCount,
581 PerObjectCongestionControlMode::TotalGasBudgetWithCap
582 )]
583 mode: PerObjectCongestionControlMode,
584 ) {
585 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
586
587 let shared_obj_0 = ObjectID::random();
589 let shared_obj_1 = ObjectID::random();
590
591 let tx_gas_budget = 100;
592
593 let max_accumulated_txn_cost_per_object_in_commit = match mode {
595 PerObjectCongestionControlMode::None => unreachable!(),
596 PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget + 1,
597 PerObjectCongestionControlMode::TotalTxCount => 2,
598 PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget - 1,
599 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 0, };
601
602 let shared_object_congestion_tracker = match mode {
603 PerObjectCongestionControlMode::None => unreachable!(),
604 PerObjectCongestionControlMode::TotalGasBudget => {
605 SharedObjectCongestionTracker::new(
610 [(shared_obj_0, 10), (shared_obj_1, 1)],
611 mode,
612 false,
613 Some(max_accumulated_txn_cost_per_object_in_commit),
614 None,
615 None,
616 0,
617 0,
618 )
619 }
620 PerObjectCongestionControlMode::TotalTxCount => {
621 SharedObjectCongestionTracker::new(
626 [(shared_obj_0, 2), (shared_obj_1, 1)],
627 mode,
628 false,
629 Some(max_accumulated_txn_cost_per_object_in_commit),
630 None,
631 None,
632 0,
633 0,
634 )
635 }
636 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
637 SharedObjectCongestionTracker::new(
642 [(shared_obj_0, 10), (shared_obj_1, 1)],
643 mode,
644 false,
645 Some(max_accumulated_txn_cost_per_object_in_commit),
646 Some(45), None,
648 0,
649 0,
650 )
651 }
652 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
653 SharedObjectCongestionTracker::new(
658 [(shared_obj_0, 750), (shared_obj_1, 0)],
659 mode,
660 false,
661 Some(max_accumulated_txn_cost_per_object_in_commit),
662 None,
663 None,
664 0,
665 0,
666 )
667 }
668 };
669
670 for mutable in [true, false].iter() {
672 let tx = build_transaction(&[(shared_obj_0, *mutable)], tx_gas_budget);
673 if let Some((_, congested_objects)) = shared_object_congestion_tracker
674 .should_defer_due_to_object_congestion(
675 shared_object_congestion_tracker.get_tx_cost(
676 Some(&execution_time_estimator),
677 &tx,
678 &mut IndirectStateObserver::new(),
679 ),
680 &tx,
681 &HashMap::new(),
682 &ConsensusCommitInfo::new_for_congestion_test(
683 0,
684 0,
685 Duration::from_micros(1_500),
686 ),
687 )
688 {
689 assert_eq!(congested_objects.len(), 1);
690 assert_eq!(congested_objects[0], shared_obj_0);
691 } else {
692 panic!("should defer");
693 }
694 }
695
696 for mutable in [true, false].iter() {
700 let tx = build_transaction(&[(shared_obj_1, *mutable)], tx_gas_budget);
701 assert!(
702 shared_object_congestion_tracker
703 .should_defer_due_to_object_congestion(
704 shared_object_congestion_tracker.get_tx_cost(
705 Some(&execution_time_estimator),
706 &tx,
707 &mut IndirectStateObserver::new(),
708 ),
709 &tx,
710 &HashMap::new(),
711 &ConsensusCommitInfo::new_for_congestion_test(
712 0,
713 0,
714 Duration::from_micros(1_500),
715 ),
716 )
717 .is_none()
718 );
719 }
720
721 for mutable_0 in [true, false].iter() {
723 for mutable_1 in [true, false].iter() {
724 let tx = build_transaction(
725 &[(shared_obj_0, *mutable_0), (shared_obj_1, *mutable_1)],
726 tx_gas_budget,
727 );
728 if let Some((_, congested_objects)) = shared_object_congestion_tracker
729 .should_defer_due_to_object_congestion(
730 shared_object_congestion_tracker.get_tx_cost(
731 Some(&execution_time_estimator),
732 &tx,
733 &mut IndirectStateObserver::new(),
734 ),
735 &tx,
736 &HashMap::new(),
737 &ConsensusCommitInfo::new_for_congestion_test(
738 0,
739 0,
740 Duration::from_micros(1_500),
741 ),
742 )
743 {
744 assert_eq!(congested_objects.len(), 1);
745 assert_eq!(congested_objects[0], shared_obj_0);
746 } else {
747 panic!("should defer");
748 }
749 }
750 }
751 }
752
753 #[rstest]
754 fn test_should_defer_return_correct_deferral_key(
755 #[values(
756 PerObjectCongestionControlMode::TotalGasBudget,
757 PerObjectCongestionControlMode::TotalTxCount,
758 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
759 PerObjectCongestionControlMode::ExecutionTimeEstimate(ExecutionTimeEstimateParams {
760 target_utilization: 0,
761 allowed_txn_cost_overage_burst_limit_us: 0,
762 max_estimate_us: u64::MAX,
763 randomness_scalar: 0,
764 stored_observations_num_included_checkpoints: 10,
765 stored_observations_limit: u64::MAX,
766 stake_weighted_median_threshold: 0,
767 default_none_duration_for_new_keys: false,
768 observations_chunk_size: None,
769 }),
770 )]
771 mode: PerObjectCongestionControlMode,
772 ) {
773 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
774
775 let shared_obj_0 = ObjectID::random();
776 let tx = build_transaction(&[(shared_obj_0, true)], 100);
777
778 let shared_object_congestion_tracker = SharedObjectCongestionTracker::new(
779 [(shared_obj_0, 1)], mode,
781 false,
782 Some(0), Some(2),
784 None,
785 0,
786 0,
787 );
788
789 let mut previously_deferred_tx_digests = HashMap::new();
791 previously_deferred_tx_digests.insert(
792 TransactionDigest::random(),
793 DeferralKey::ConsensusRound {
794 future_round: 10,
795 deferred_from_round: 5,
796 },
797 );
798
799 if let Some((
801 DeferralKey::ConsensusRound {
802 future_round,
803 deferred_from_round,
804 },
805 _,
806 )) = shared_object_congestion_tracker.should_defer_due_to_object_congestion(
807 shared_object_congestion_tracker.get_tx_cost(
808 Some(&execution_time_estimator),
809 &tx,
810 &mut IndirectStateObserver::new(),
811 ),
812 &tx,
813 &previously_deferred_tx_digests,
814 &ConsensusCommitInfo::new_for_congestion_test(
815 10,
816 10,
817 Duration::from_micros(10_000_000),
818 ),
819 ) {
820 assert_eq!(future_round, 11);
821 assert_eq!(deferred_from_round, 10);
822 } else {
823 panic!("should defer");
824 }
825
826 previously_deferred_tx_digests.insert(
828 *tx.digest(),
829 DeferralKey::Randomness {
830 deferred_from_round: 4,
831 },
832 );
833
834 if let Some((
836 DeferralKey::ConsensusRound {
837 future_round,
838 deferred_from_round,
839 },
840 _,
841 )) = shared_object_congestion_tracker.should_defer_due_to_object_congestion(
842 shared_object_congestion_tracker.get_tx_cost(
843 Some(&execution_time_estimator),
844 &tx,
845 &mut IndirectStateObserver::new(),
846 ),
847 &tx,
848 &previously_deferred_tx_digests,
849 &ConsensusCommitInfo::new_for_congestion_test(
850 10,
851 10,
852 Duration::from_micros(10_000_000),
853 ),
854 ) {
855 assert_eq!(future_round, 11);
856 assert_eq!(deferred_from_round, 4);
857 } else {
858 panic!("should defer");
859 }
860
861 previously_deferred_tx_digests.insert(
863 *tx.digest(),
864 DeferralKey::ConsensusRound {
865 future_round: 10,
866 deferred_from_round: 5,
867 },
868 );
869
870 if let Some((
872 DeferralKey::ConsensusRound {
873 future_round,
874 deferred_from_round,
875 },
876 _,
877 )) = shared_object_congestion_tracker.should_defer_due_to_object_congestion(
878 shared_object_congestion_tracker.get_tx_cost(
879 Some(&execution_time_estimator),
880 &tx,
881 &mut IndirectStateObserver::new(),
882 ),
883 &tx,
884 &previously_deferred_tx_digests,
885 &ConsensusCommitInfo::new_for_congestion_test(
886 10,
887 10,
888 Duration::from_micros(10_000_000),
889 ),
890 ) {
891 assert_eq!(future_round, 11);
892 assert_eq!(deferred_from_round, 5);
893 } else {
894 panic!("should defer");
895 }
896 }
897
898 #[rstest]
899 fn test_should_defer_allow_overage(
900 #[values(
901 PerObjectCongestionControlMode::TotalGasBudget,
902 PerObjectCongestionControlMode::TotalTxCount,
903 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
904 PerObjectCongestionControlMode::ExecutionTimeEstimate(ExecutionTimeEstimateParams {
905 target_utilization: 16,
906 allowed_txn_cost_overage_burst_limit_us: 0,
907 randomness_scalar: 0,
908 max_estimate_us: u64::MAX,
909 stored_observations_num_included_checkpoints: 10,
910 stored_observations_limit: u64::MAX,
911 stake_weighted_median_threshold: 0,
912 default_none_duration_for_new_keys: false,
913 observations_chunk_size: None,
914 }),
915 )]
916 mode: PerObjectCongestionControlMode,
917 ) {
918 telemetry_subscribers::init_for_testing();
919
920 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
921
922 let shared_obj_0 = ObjectID::random();
924 let shared_obj_1 = ObjectID::random();
925
926 let tx_gas_budget = 100;
927
928 let max_accumulated_txn_cost_per_object_in_commit = match mode {
931 PerObjectCongestionControlMode::None => unreachable!(),
932 PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget + 1,
933 PerObjectCongestionControlMode::TotalTxCount => 2,
934 PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget - 1,
935 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 0, };
937
938 let shared_object_congestion_tracker = match mode {
939 PerObjectCongestionControlMode::None => unreachable!(),
940 PerObjectCongestionControlMode::TotalGasBudget => {
941 SharedObjectCongestionTracker::new(
946 [(shared_obj_0, 102), (shared_obj_1, 90)],
947 mode,
948 false,
949 Some(max_accumulated_txn_cost_per_object_in_commit),
950 None,
951 None,
952 max_accumulated_txn_cost_per_object_in_commit * 10,
953 0,
954 )
955 }
956 PerObjectCongestionControlMode::TotalTxCount => {
957 SharedObjectCongestionTracker::new(
962 [(shared_obj_0, 3), (shared_obj_1, 2)],
963 mode,
964 false,
965 Some(max_accumulated_txn_cost_per_object_in_commit),
966 None,
967 None,
968 max_accumulated_txn_cost_per_object_in_commit * 10,
969 0,
970 )
971 }
972 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
973 SharedObjectCongestionTracker::new(
978 [(shared_obj_0, 100), (shared_obj_1, 90)],
979 mode,
980 false,
981 Some(max_accumulated_txn_cost_per_object_in_commit),
982 Some(45), None,
984 max_accumulated_txn_cost_per_object_in_commit * 10,
985 0,
986 )
987 }
988 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
989 SharedObjectCongestionTracker::new(
994 [(shared_obj_0, 1_700_000), (shared_obj_1, 300_000)],
995 mode,
996 false,
997 Some(max_accumulated_txn_cost_per_object_in_commit),
998 None,
999 None,
1000 max_accumulated_txn_cost_per_object_in_commit * 10,
1001 0,
1002 )
1003 }
1004 };
1005
1006 for mutable in [true, false].iter() {
1008 let tx = build_transaction(&[(shared_obj_0, *mutable)], tx_gas_budget);
1009 if let Some((_, congested_objects)) = shared_object_congestion_tracker
1010 .should_defer_due_to_object_congestion(
1011 shared_object_congestion_tracker.get_tx_cost(
1012 Some(&execution_time_estimator),
1013 &tx,
1014 &mut IndirectStateObserver::new(),
1015 ),
1016 &tx,
1017 &HashMap::new(),
1018 &ConsensusCommitInfo::new_for_congestion_test(
1019 0,
1020 0,
1021 Duration::from_micros(10_000_000),
1022 ),
1023 )
1024 {
1025 assert_eq!(congested_objects.len(), 1);
1026 assert_eq!(congested_objects[0], shared_obj_0);
1027 } else {
1028 panic!("should defer");
1029 }
1030 }
1031
1032 for mutable in [true, false].iter() {
1034 let tx = build_transaction(&[(shared_obj_1, *mutable)], tx_gas_budget);
1035 assert!(
1036 shared_object_congestion_tracker
1037 .should_defer_due_to_object_congestion(
1038 shared_object_congestion_tracker.get_tx_cost(
1039 Some(&execution_time_estimator),
1040 &tx,
1041 &mut IndirectStateObserver::new(),
1042 ),
1043 &tx,
1044 &HashMap::new(),
1045 &ConsensusCommitInfo::new_for_congestion_test(
1046 0,
1047 0,
1048 Duration::from_micros(10_000_000)
1049 ),
1050 )
1051 .is_none()
1052 );
1053 }
1054
1055 for mutable_0 in [true, false].iter() {
1057 for mutable_1 in [true, false].iter() {
1058 let tx = build_transaction(
1059 &[(shared_obj_0, *mutable_0), (shared_obj_1, *mutable_1)],
1060 tx_gas_budget,
1061 );
1062 if let Some((_, congested_objects)) = shared_object_congestion_tracker
1063 .should_defer_due_to_object_congestion(
1064 shared_object_congestion_tracker.get_tx_cost(
1065 Some(&execution_time_estimator),
1066 &tx,
1067 &mut IndirectStateObserver::new(),
1068 ),
1069 &tx,
1070 &HashMap::new(),
1071 &ConsensusCommitInfo::new_for_congestion_test(
1072 0,
1073 0,
1074 Duration::from_micros(10_000_000),
1075 ),
1076 )
1077 {
1078 assert_eq!(congested_objects.len(), 1);
1079 assert_eq!(congested_objects[0], shared_obj_0);
1080 } else {
1081 panic!("should defer");
1082 }
1083 }
1084 }
1085 }
1086
1087 #[rstest]
1088 fn test_should_defer_allow_overage_with_burst(
1089 #[values(
1090 PerObjectCongestionControlMode::TotalGasBudget,
1091 PerObjectCongestionControlMode::TotalTxCount,
1092 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
1093 PerObjectCongestionControlMode::ExecutionTimeEstimate(ExecutionTimeEstimateParams {
1094 target_utilization: 16,
1095 allowed_txn_cost_overage_burst_limit_us: 1_500_000,
1096 randomness_scalar: 0,
1097 max_estimate_us: u64::MAX,
1098 stored_observations_num_included_checkpoints: 10,
1099 stored_observations_limit: u64::MAX,
1100 stake_weighted_median_threshold: 0,
1101 default_none_duration_for_new_keys: false,
1102 observations_chunk_size: None,
1103 }),
1104 )]
1105 mode: PerObjectCongestionControlMode,
1106 ) {
1107 telemetry_subscribers::init_for_testing();
1108
1109 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
1110
1111 let shared_obj_0 = ObjectID::random();
1112 let shared_obj_1 = ObjectID::random();
1113
1114 let tx_gas_budget = 100;
1115
1116 let max_accumulated_txn_cost_per_object_in_commit = match mode {
1119 PerObjectCongestionControlMode::None => unreachable!(),
1120 PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget,
1121 PerObjectCongestionControlMode::TotalTxCount => 2,
1122 PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget,
1123 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 0, };
1125
1126 let allowed_txn_cost_overage_burst_per_object_in_commit = match mode {
1128 PerObjectCongestionControlMode::None => unreachable!(),
1129 PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget * 2,
1130 PerObjectCongestionControlMode::TotalTxCount => 2,
1131 PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget * 2,
1132 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 0, };
1134
1135 let shared_object_congestion_tracker = match mode {
1136 PerObjectCongestionControlMode::None => unreachable!(),
1137 PerObjectCongestionControlMode::TotalGasBudget => {
1138 SharedObjectCongestionTracker::new(
1146 [(shared_obj_0, 301), (shared_obj_1, 199)],
1147 mode,
1148 false,
1149 Some(max_accumulated_txn_cost_per_object_in_commit),
1150 None,
1151 None,
1152 max_accumulated_txn_cost_per_object_in_commit * 10,
1153 allowed_txn_cost_overage_burst_per_object_in_commit,
1154 )
1155 }
1156 PerObjectCongestionControlMode::TotalTxCount => {
1157 SharedObjectCongestionTracker::new(
1165 [(shared_obj_0, 5), (shared_obj_1, 4)],
1166 mode,
1167 false,
1168 Some(max_accumulated_txn_cost_per_object_in_commit),
1169 None,
1170 None,
1171 max_accumulated_txn_cost_per_object_in_commit * 10,
1172 allowed_txn_cost_overage_burst_per_object_in_commit,
1173 )
1174 }
1175 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
1176 SharedObjectCongestionTracker::new(
1184 [(shared_obj_0, 301), (shared_obj_1, 250)],
1185 mode,
1186 false,
1187 Some(max_accumulated_txn_cost_per_object_in_commit),
1188 Some(45), None,
1190 max_accumulated_txn_cost_per_object_in_commit * 10,
1191 allowed_txn_cost_overage_burst_per_object_in_commit,
1192 )
1193 }
1194 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
1195 SharedObjectCongestionTracker::new(
1203 [(shared_obj_0, 4_000_000), (shared_obj_1, 2_000_000)],
1204 mode,
1205 false,
1206 Some(max_accumulated_txn_cost_per_object_in_commit),
1207 None,
1208 None,
1209 max_accumulated_txn_cost_per_object_in_commit * 10,
1210 allowed_txn_cost_overage_burst_per_object_in_commit,
1211 )
1212 }
1213 };
1214
1215 for mutable in [true, false].iter() {
1217 let tx = build_transaction(&[(shared_obj_0, *mutable)], tx_gas_budget);
1218 if let Some((_, congested_objects)) = shared_object_congestion_tracker
1219 .should_defer_due_to_object_congestion(
1220 shared_object_congestion_tracker.get_tx_cost(
1221 Some(&execution_time_estimator),
1222 &tx,
1223 &mut IndirectStateObserver::new(),
1224 ),
1225 &tx,
1226 &HashMap::new(),
1227 &ConsensusCommitInfo::new_for_congestion_test(
1228 0,
1229 0,
1230 Duration::from_micros(10_000_000),
1231 ),
1232 )
1233 {
1234 assert_eq!(congested_objects.len(), 1);
1235 assert_eq!(congested_objects[0], shared_obj_0);
1236 } else {
1237 panic!("should defer");
1238 }
1239 }
1240
1241 for mutable in [true, false].iter() {
1244 let tx = build_transaction(&[(shared_obj_1, *mutable)], tx_gas_budget);
1245 assert!(
1246 shared_object_congestion_tracker
1247 .should_defer_due_to_object_congestion(
1248 shared_object_congestion_tracker.get_tx_cost(
1249 Some(&execution_time_estimator),
1250 &tx,
1251 &mut IndirectStateObserver::new(),
1252 ),
1253 &tx,
1254 &HashMap::new(),
1255 &ConsensusCommitInfo::new_for_congestion_test(
1256 0,
1257 0,
1258 Duration::from_micros(10_000_000)
1259 ),
1260 )
1261 .is_none()
1262 );
1263 }
1264
1265 for mutable_0 in [true, false].iter() {
1267 for mutable_1 in [true, false].iter() {
1268 let tx = build_transaction(
1269 &[(shared_obj_0, *mutable_0), (shared_obj_1, *mutable_1)],
1270 tx_gas_budget,
1271 );
1272 if let Some((_, congested_objects)) = shared_object_congestion_tracker
1273 .should_defer_due_to_object_congestion(
1274 shared_object_congestion_tracker.get_tx_cost(
1275 Some(&execution_time_estimator),
1276 &tx,
1277 &mut IndirectStateObserver::new(),
1278 ),
1279 &tx,
1280 &HashMap::new(),
1281 &ConsensusCommitInfo::new_for_congestion_test(
1282 0,
1283 0,
1284 Duration::from_micros(10_000_000),
1285 ),
1286 )
1287 {
1288 assert_eq!(congested_objects.len(), 1);
1289 assert_eq!(congested_objects[0], shared_obj_0);
1290 } else {
1291 panic!("should defer");
1292 }
1293 }
1294 }
1295 }
1296
1297 #[rstest]
1298 fn test_bump_object_execution_cost(
1299 #[values(
1300 PerObjectCongestionControlMode::TotalGasBudget,
1301 PerObjectCongestionControlMode::TotalTxCount,
1302 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
1303 PerObjectCongestionControlMode::ExecutionTimeEstimate(ExecutionTimeEstimateParams {
1304 target_utilization: 0,
1306 allowed_txn_cost_overage_burst_limit_us: 0,
1307 randomness_scalar: 0,
1308 max_estimate_us: u64::MAX,
1309 stored_observations_num_included_checkpoints: 10,
1310 stored_observations_limit: u64::MAX,
1311 stake_weighted_median_threshold: 0,
1312 default_none_duration_for_new_keys: false,
1313 observations_chunk_size: None,
1314 }),
1315 )]
1316 mode: PerObjectCongestionControlMode,
1317 ) {
1318 telemetry_subscribers::init_for_testing();
1319
1320 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
1321
1322 let object_id_0 = ObjectID::random();
1323 let object_id_1 = ObjectID::random();
1324 let object_id_2 = ObjectID::random();
1325
1326 let cap_factor = Some(1);
1327
1328 let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new(
1329 [(object_id_0, 5), (object_id_1, 10)],
1330 mode,
1331 false,
1332 Some(0), cap_factor,
1334 None,
1335 0,
1336 0,
1337 );
1338 assert_eq!(shared_object_congestion_tracker.max_cost(), 10);
1339
1340 let cert = build_transaction(&[(object_id_0, false), (object_id_1, false)], 10);
1342 shared_object_congestion_tracker.bump_object_execution_cost(
1343 shared_object_congestion_tracker.get_tx_cost(
1344 Some(&execution_time_estimator),
1345 &cert,
1346 &mut IndirectStateObserver::new(),
1347 ),
1348 &cert,
1349 );
1350 assert_eq!(
1351 shared_object_congestion_tracker,
1352 SharedObjectCongestionTracker::new(
1353 [(object_id_0, 5), (object_id_1, 10)],
1354 mode,
1355 false,
1356 Some(0), cap_factor,
1358 None,
1359 0,
1360 0,
1361 )
1362 );
1363 assert_eq!(shared_object_congestion_tracker.max_cost(), 10);
1364
1365 let cert = build_transaction(&[(object_id_0, true), (object_id_1, false)], 10);
1367 shared_object_congestion_tracker.bump_object_execution_cost(
1368 shared_object_congestion_tracker.get_tx_cost(
1369 Some(&execution_time_estimator),
1370 &cert,
1371 &mut IndirectStateObserver::new(),
1372 ),
1373 &cert,
1374 );
1375 let expected_object_0_cost = match mode {
1376 PerObjectCongestionControlMode::None => unreachable!(),
1377 PerObjectCongestionControlMode::TotalGasBudget => 20,
1378 PerObjectCongestionControlMode::TotalTxCount => 11,
1379 PerObjectCongestionControlMode::TotalGasBudgetWithCap => 13, PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 1_010,
1381 };
1382 assert_eq!(
1383 shared_object_congestion_tracker,
1384 SharedObjectCongestionTracker::new(
1385 [(object_id_0, expected_object_0_cost), (object_id_1, 10)],
1386 mode,
1387 false,
1388 Some(0), cap_factor,
1390 None,
1391 0,
1392 0,
1393 )
1394 );
1395 assert_eq!(
1396 shared_object_congestion_tracker.max_cost(),
1397 expected_object_0_cost
1398 );
1399
1400 let cert = build_transaction(
1402 &[
1403 (object_id_0, true),
1404 (object_id_1, true),
1405 (object_id_2, true),
1406 ],
1407 10,
1408 );
1409 let expected_object_cost = match mode {
1410 PerObjectCongestionControlMode::None => unreachable!(),
1411 PerObjectCongestionControlMode::TotalGasBudget => 30,
1412 PerObjectCongestionControlMode::TotalTxCount => 12,
1413 PerObjectCongestionControlMode::TotalGasBudgetWithCap => 17, PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 2_010,
1415 };
1416 shared_object_congestion_tracker.bump_object_execution_cost(
1417 shared_object_congestion_tracker.get_tx_cost(
1418 Some(&execution_time_estimator),
1419 &cert,
1420 &mut IndirectStateObserver::new(),
1421 ),
1422 &cert,
1423 );
1424 assert_eq!(
1425 shared_object_congestion_tracker,
1426 SharedObjectCongestionTracker::new(
1427 [
1428 (object_id_0, expected_object_cost),
1429 (object_id_1, expected_object_cost),
1430 (object_id_2, expected_object_cost)
1431 ],
1432 mode,
1433 false,
1434 Some(0), cap_factor,
1436 None,
1437 0,
1438 0,
1439 )
1440 );
1441 assert_eq!(
1442 shared_object_congestion_tracker.max_cost(),
1443 expected_object_cost
1444 );
1445
1446 let cert = build_programmable_transaction(
1448 &[
1449 (object_id_0, true),
1450 (object_id_1, true),
1451 (object_id_2, true),
1452 ],
1453 7,
1454 30,
1455 );
1456 let expected_object_cost = match mode {
1457 PerObjectCongestionControlMode::None => unreachable!(),
1458 PerObjectCongestionControlMode::TotalGasBudget => 60,
1459 PerObjectCongestionControlMode::TotalTxCount => 13,
1460 PerObjectCongestionControlMode::TotalGasBudgetWithCap => 45, PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 9_010,
1463 };
1464 shared_object_congestion_tracker.bump_object_execution_cost(
1465 shared_object_congestion_tracker.get_tx_cost(
1466 Some(&execution_time_estimator),
1467 &cert,
1468 &mut IndirectStateObserver::new(),
1469 ),
1470 &cert,
1471 );
1472 assert_eq!(
1473 shared_object_congestion_tracker,
1474 SharedObjectCongestionTracker::new(
1475 [
1476 (object_id_0, expected_object_cost),
1477 (object_id_1, expected_object_cost),
1478 (object_id_2, expected_object_cost)
1479 ],
1480 mode,
1481 false,
1482 Some(0), cap_factor,
1484 None,
1485 0,
1486 0,
1487 )
1488 );
1489 assert_eq!(
1490 shared_object_congestion_tracker.max_cost(),
1491 expected_object_cost
1492 );
1493 }
1494
1495 #[rstest]
1496 fn test_accumulated_debts(
1497 #[values(
1498 PerObjectCongestionControlMode::TotalGasBudget,
1499 PerObjectCongestionControlMode::TotalTxCount,
1500 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
1501 PerObjectCongestionControlMode::ExecutionTimeEstimate(ExecutionTimeEstimateParams {
1502 target_utilization: 100,
1503 allowed_txn_cost_overage_burst_limit_us: 1_600 * 5,
1505 randomness_scalar: 0,
1506 max_estimate_us: u64::MAX,
1507 stored_observations_num_included_checkpoints: 10,
1508 stored_observations_limit: u64::MAX,
1509 stake_weighted_median_threshold: 0,
1510 default_none_duration_for_new_keys: false,
1511 observations_chunk_size: None,
1512 }),
1513 )]
1514 mode: PerObjectCongestionControlMode,
1515 ) {
1516 telemetry_subscribers::init_for_testing();
1517
1518 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
1519
1520 let shared_obj_0 = ObjectID::random();
1522 let shared_obj_1 = ObjectID::random();
1523
1524 let tx_gas_budget = 100;
1525
1526 let max_accumulated_txn_cost_per_object_in_commit = match mode {
1529 PerObjectCongestionControlMode::None => unreachable!(),
1530 PerObjectCongestionControlMode::TotalGasBudget
1531 | PerObjectCongestionControlMode::TotalGasBudgetWithCap => 90,
1532 PerObjectCongestionControlMode::TotalTxCount => 2,
1533 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => 0, };
1535
1536 let mut shared_object_congestion_tracker = match mode {
1537 PerObjectCongestionControlMode::None => unreachable!(),
1538 PerObjectCongestionControlMode::TotalGasBudget => {
1539 SharedObjectCongestionTracker::new(
1541 [(shared_obj_0, 80), (shared_obj_1, 80)],
1542 mode,
1543 false,
1544 Some(max_accumulated_txn_cost_per_object_in_commit),
1545 None,
1546 None,
1547 max_accumulated_txn_cost_per_object_in_commit * 10,
1548 max_accumulated_txn_cost_per_object_in_commit * 5,
1550 )
1551 }
1552 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
1553 SharedObjectCongestionTracker::new(
1555 [(shared_obj_0, 80), (shared_obj_1, 80)],
1556 mode,
1557 false,
1558 Some(max_accumulated_txn_cost_per_object_in_commit),
1559 Some(45),
1560 None,
1561 max_accumulated_txn_cost_per_object_in_commit * 10,
1562 max_accumulated_txn_cost_per_object_in_commit * 5,
1564 )
1565 }
1566 PerObjectCongestionControlMode::TotalTxCount => {
1567 SharedObjectCongestionTracker::new(
1569 [(shared_obj_0, 2), (shared_obj_1, 2)],
1570 mode,
1571 false,
1572 Some(max_accumulated_txn_cost_per_object_in_commit),
1573 None,
1574 None,
1575 max_accumulated_txn_cost_per_object_in_commit * 10,
1576 max_accumulated_txn_cost_per_object_in_commit * 5,
1578 )
1579 }
1580 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
1581 SharedObjectCongestionTracker::new(
1583 [(shared_obj_0, 500), (shared_obj_1, 500)],
1584 mode,
1585 false,
1586 Some(max_accumulated_txn_cost_per_object_in_commit),
1587 None,
1588 None,
1589 max_accumulated_txn_cost_per_object_in_commit * 10,
1590 max_accumulated_txn_cost_per_object_in_commit * 5,
1592 )
1593 }
1594 };
1595
1596 for mutable in [true, false].iter() {
1598 let tx = build_transaction(&[(shared_obj_0, *mutable)], tx_gas_budget);
1599 shared_object_congestion_tracker.bump_object_execution_cost(
1600 shared_object_congestion_tracker.get_tx_cost(
1601 Some(&execution_time_estimator),
1602 &tx,
1603 &mut IndirectStateObserver::new(),
1604 ),
1605 &tx,
1606 );
1607 }
1608
1609 let accumulated_debts = shared_object_congestion_tracker.accumulated_debts(
1611 &ConsensusCommitInfo::new_for_congestion_test(0, 0, Duration::from_micros(800)),
1612 );
1613 assert_eq!(accumulated_debts.len(), 1);
1614 match mode {
1615 PerObjectCongestionControlMode::None => unreachable!(),
1616 PerObjectCongestionControlMode::TotalGasBudget => {
1617 assert_eq!(accumulated_debts[0], (shared_obj_0, 90)); }
1619 PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
1620 assert_eq!(accumulated_debts[0], (shared_obj_0, 80)); }
1622 PerObjectCongestionControlMode::TotalTxCount => {
1623 assert_eq!(accumulated_debts[0], (shared_obj_0, 1)); }
1625 PerObjectCongestionControlMode::ExecutionTimeEstimate(_) => {
1626 assert_eq!(accumulated_debts[0], (shared_obj_0, 700));
1628 }
1629 }
1630 }
1631
1632 #[test]
1633 fn test_accumulated_debts_empty() {
1634 let object_id_0 = ObjectID::random();
1635 let object_id_1 = ObjectID::random();
1636 let object_id_2 = ObjectID::random();
1637
1638 let shared_object_congestion_tracker = SharedObjectCongestionTracker::new(
1639 [(object_id_0, 5), (object_id_1, 10), (object_id_2, 100)],
1640 PerObjectCongestionControlMode::TotalGasBudget,
1641 false,
1642 Some(100),
1643 None,
1644 None,
1645 0,
1646 0,
1647 );
1648
1649 let accumulated_debts = shared_object_congestion_tracker.accumulated_debts(
1650 &ConsensusCommitInfo::new_for_congestion_test(0, 0, Duration::ZERO),
1651 );
1652 assert!(accumulated_debts.is_empty());
1653 }
1654
1655 #[test]
1656 fn test_tx_cost_absolute_cap() {
1657 let execution_time_estimator = ExecutionTimeEstimator::new_for_testing();
1658
1659 let object_id_0 = ObjectID::random();
1660 let object_id_1 = ObjectID::random();
1661 let object_id_2 = ObjectID::random();
1662
1663 let tx_gas_budget = 2000;
1664
1665 let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new(
1666 [(object_id_0, 5), (object_id_1, 10), (object_id_2, 100)],
1667 PerObjectCongestionControlMode::TotalGasBudgetWithCap,
1668 false,
1669 Some(100),
1670 Some(1000),
1671 Some(2),
1672 1000,
1673 0,
1674 );
1675
1676 let tx = build_transaction(
1678 &[
1679 (object_id_0, false),
1680 (object_id_1, false),
1681 (object_id_2, true),
1682 ],
1683 tx_gas_budget,
1684 );
1685
1686 let tx_cost = shared_object_congestion_tracker.get_tx_cost(
1689 Some(&execution_time_estimator),
1690 &tx,
1691 &mut IndirectStateObserver::new(),
1692 );
1693 assert!(
1694 shared_object_congestion_tracker
1695 .should_defer_due_to_object_congestion(
1696 tx_cost,
1697 &tx,
1698 &HashMap::new(),
1699 &ConsensusCommitInfo::new_for_congestion_test(0, 0, Duration::ZERO),
1700 )
1701 .is_none()
1702 );
1703
1704 shared_object_congestion_tracker.bump_object_execution_cost(tx_cost, &tx);
1706 assert_eq!(300, shared_object_congestion_tracker.max_cost());
1707
1708 let accumulated_debts = shared_object_congestion_tracker.accumulated_debts(
1710 &ConsensusCommitInfo::new_for_congestion_test(0, 0, Duration::ZERO),
1711 );
1712 assert_eq!(accumulated_debts.len(), 1);
1713 assert_eq!(accumulated_debts[0], (object_id_2, 200));
1714 }
1715}