Skip to content

Commit

Permalink
Add burst mode for congestion control (#20631)
Browse files Browse the repository at this point in the history
  • Loading branch information
aschran authored Dec 30, 2024
1 parent 1a27a82 commit 1013f93
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 15 deletions.
186 changes: 173 additions & 13 deletions crates/sui-core/src/authority/shared_object_congestion_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct SharedObjectCongestionTracker {
gas_budget_based_txn_cost_cap_factor: Option<u64>,
gas_budget_based_txn_cost_absolute_cap: Option<u64>,
max_txn_cost_overage_per_object_in_commit: u64,
allowed_txn_cost_overage_burst_per_object_in_commit: u64,
}

impl SharedObjectCongestionTracker {
Expand All @@ -44,7 +45,13 @@ impl SharedObjectCongestionTracker {
gas_budget_based_txn_cost_cap_factor: Option<u64>,
gas_budget_based_txn_cost_absolute_cap_commit_count: Option<u64>,
max_txn_cost_overage_per_object_in_commit: u64,
allowed_txn_cost_overage_burst_per_object_in_commit: u64,
) -> Self {
assert!(
allowed_txn_cost_overage_burst_per_object_in_commit <= max_txn_cost_overage_per_object_in_commit,
"burst limit bust 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}"
);

let object_execution_cost: HashMap<ObjectID, u64> =
initial_object_debts.into_iter().collect();
let max_accumulated_txn_cost_per_object_in_commit =
Expand Down Expand Up @@ -75,6 +82,7 @@ impl SharedObjectCongestionTracker {
gas_budget_based_txn_cost_cap_factor,
gas_budget_based_txn_cost_absolute_cap,
max_txn_cost_overage_per_object_in_commit,
allowed_txn_cost_overage_burst_per_object_in_commit,
}
}

Expand Down Expand Up @@ -107,6 +115,9 @@ impl SharedObjectCongestionTracker {
protocol_config
.max_txn_cost_overage_per_object_in_commit_as_option()
.unwrap_or(0),
protocol_config
.allowed_txn_cost_overage_burst_per_object_in_commit_as_option()
.unwrap_or(0),
))
}

Expand Down Expand Up @@ -149,20 +160,16 @@ impl SharedObjectCongestionTracker {
return None;
}
let start_cost = self.compute_tx_start_at_cost(&shared_input_objects);
let end_cost = start_cost.saturating_add(tx_cost);

// Allow tx if it's within budget.
if start_cost.saturating_add(tx_cost) <= self.max_accumulated_txn_cost_per_object_in_commit
{
return None;
}

// Allow over-budget tx if it's not above the overage limit.
if start_cost <= self.max_accumulated_txn_cost_per_object_in_commit
&& start_cost.saturating_add(tx_cost)
<= self
.max_accumulated_txn_cost_per_object_in_commit
.saturating_add(self.max_txn_cost_overage_per_object_in_commit)
{
// Allow tx if it's within configured limits.
let burst_limit = self
.max_accumulated_txn_cost_per_object_in_commit
.saturating_add(self.allowed_txn_cost_overage_burst_per_object_in_commit);
let absolute_limit = self
.max_accumulated_txn_cost_per_object_in_commit
.saturating_add(self.max_txn_cost_overage_per_object_in_commit);
if start_cost <= burst_limit && end_cost <= absolute_limit {
return None;
}

Expand Down Expand Up @@ -332,6 +339,7 @@ mod object_cost_tests {
None,
None,
0,
0,
);

let shared_input_objects = construct_shared_input_objects(&[(object_id_0, false)]);
Expand Down Expand Up @@ -483,6 +491,7 @@ mod object_cost_tests {
None,
None,
0,
0,
)
}
PerObjectCongestionControlMode::TotalTxCount => {
Expand All @@ -497,6 +506,7 @@ mod object_cost_tests {
None,
None,
0,
0,
)
}
PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
Expand All @@ -511,6 +521,7 @@ mod object_cost_tests {
Some(45), // Make the cap just less than the gas budget, there are 1 objects in tx.
None,
0,
0,
)
}
};
Expand Down Expand Up @@ -576,6 +587,7 @@ mod object_cost_tests {
Some(2),
None,
0,
0,
);

// Insert a random pre-existing transaction.
Expand Down Expand Up @@ -700,6 +712,7 @@ mod object_cost_tests {
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
0,
)
}
PerObjectCongestionControlMode::TotalTxCount => {
Expand All @@ -714,6 +727,7 @@ mod object_cost_tests {
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
0,
)
}
PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
Expand All @@ -728,6 +742,7 @@ mod object_cost_tests {
Some(45), // Make the cap just less than the gas budget, there are 1 objects in tx.
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
0,
)
}
};
Expand Down Expand Up @@ -772,6 +787,138 @@ mod object_cost_tests {
}
}

#[rstest]
fn test_should_defer_allow_overage_with_burst(
#[values(
PerObjectCongestionControlMode::TotalGasBudget,
PerObjectCongestionControlMode::TotalTxCount,
PerObjectCongestionControlMode::TotalGasBudgetWithCap
)]
mode: PerObjectCongestionControlMode,
) {
telemetry_subscribers::init_for_testing();

let shared_obj_0 = ObjectID::random();
let shared_obj_1 = ObjectID::random();

let tx_gas_budget = 100;

// Set max_accumulated_txn_cost_per_object_in_commit to allow 1 transaction to go through
// before overage occurs.
let max_accumulated_txn_cost_per_object_in_commit = match mode {
PerObjectCongestionControlMode::None => unreachable!(),
PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget,
PerObjectCongestionControlMode::TotalTxCount => 2,
PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget,
};

// Set burst limit to allow 1 extra transaction to go through.
let allowed_txn_cost_overage_burst_per_object_in_commit = match mode {
PerObjectCongestionControlMode::None => unreachable!(),
PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget * 2,
PerObjectCongestionControlMode::TotalTxCount => 2,
PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget * 2,
};

let shared_object_congestion_tracker = match mode {
PerObjectCongestionControlMode::None => unreachable!(),
PerObjectCongestionControlMode::TotalGasBudget => {
// Construct object execution cost as following
// 199 301
// object 0: |
// object 1: |
//
// burst limit is 100 + 200 = 300
// tx cost is 100 (gas budget)
SharedObjectCongestionTracker::new(
[(shared_obj_0, 301), (shared_obj_1, 199)],
mode,
Some(max_accumulated_txn_cost_per_object_in_commit),
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
allowed_txn_cost_overage_burst_per_object_in_commit,
)
}
PerObjectCongestionControlMode::TotalTxCount => {
// Construct object execution cost as following
// 4 5
// object 0: |
// object 1: |
//
// burst limit is 2 + 2 = 4
// tx cost is 1 (tx count)
SharedObjectCongestionTracker::new(
[(shared_obj_0, 5), (shared_obj_1, 4)],
mode,
Some(max_accumulated_txn_cost_per_object_in_commit),
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
allowed_txn_cost_overage_burst_per_object_in_commit,
)
}
PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
// Construct object execution cost as following
// 250 301
// object 0: |
// object 1: |
//
// burst limit is 100 + 200 = 300
// tx cost is 90 (gas budget capped at 45*(1 move call + 1 input))
SharedObjectCongestionTracker::new(
[(shared_obj_0, 301), (shared_obj_1, 250)],
mode,
Some(max_accumulated_txn_cost_per_object_in_commit),
Some(45), // Make the cap just less than the gas budget, there are 1 objects in tx.
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
allowed_txn_cost_overage_burst_per_object_in_commit,
)
}
};

// Read/write to object 0 should be deferred.
for mutable in [true, false].iter() {
let tx = build_transaction(&[(shared_obj_0, *mutable)], tx_gas_budget);
if let Some((_, congested_objects)) = shared_object_congestion_tracker
.should_defer_due_to_object_congestion(&tx, &HashMap::new(), 0)
{
assert_eq!(congested_objects.len(), 1);
assert_eq!(congested_objects[0], shared_obj_0);
} else {
panic!("should defer");
}
}

// Read/write to object 1 should go through even though the budget is exceeded
// even before the cost of this tx is considered.
for mutable in [true, false].iter() {
let tx = build_transaction(&[(shared_obj_1, *mutable)], tx_gas_budget);
assert!(shared_object_congestion_tracker
.should_defer_due_to_object_congestion(&tx, &HashMap::new(), 0,)
.is_none());
}

// Transactions touching both objects should be deferred, with object 0 as the congested object.
for mutable_0 in [true, false].iter() {
for mutable_1 in [true, false].iter() {
let tx = build_transaction(
&[(shared_obj_0, *mutable_0), (shared_obj_1, *mutable_1)],
tx_gas_budget,
);
if let Some((_, congested_objects)) = shared_object_congestion_tracker
.should_defer_due_to_object_congestion(&tx, &HashMap::new(), 0)
{
assert_eq!(congested_objects.len(), 1);
assert_eq!(congested_objects[0], shared_obj_0);
} else {
panic!("should defer");
}
}
}
}

#[rstest]
fn test_bump_object_execution_cost(
#[values(
Expand All @@ -794,6 +941,7 @@ mod object_cost_tests {
cap_factor,
None,
0,
0,
);
assert_eq!(shared_object_congestion_tracker.max_cost(), 10);

Expand All @@ -809,6 +957,7 @@ mod object_cost_tests {
cap_factor,
None,
0,
0,
)
);
assert_eq!(shared_object_congestion_tracker.max_cost(), 10);
Expand All @@ -831,6 +980,7 @@ mod object_cost_tests {
cap_factor,
None,
0,
0,
)
);
assert_eq!(
Expand Down Expand Up @@ -867,6 +1017,7 @@ mod object_cost_tests {
cap_factor,
None,
0,
0,
)
);
assert_eq!(
Expand Down Expand Up @@ -904,6 +1055,7 @@ mod object_cost_tests {
cap_factor,
None,
0,
0,
)
);
assert_eq!(
Expand Down Expand Up @@ -949,6 +1101,8 @@ mod object_cost_tests {
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
// Set a burst limit to verify that it does not affect debt calculation.
max_accumulated_txn_cost_per_object_in_commit * 5,
)
}
PerObjectCongestionControlMode::TotalGasBudgetWithCap => {
Expand All @@ -960,6 +1114,8 @@ mod object_cost_tests {
Some(45),
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
// Set a burst limit to verify that it does not affect debt calculation.
max_accumulated_txn_cost_per_object_in_commit * 5,
)
}
PerObjectCongestionControlMode::TotalTxCount => {
Expand All @@ -971,6 +1127,8 @@ mod object_cost_tests {
None,
None,
max_accumulated_txn_cost_per_object_in_commit * 10,
// Set a burst limit to verify that it does not affect debt calculation.
max_accumulated_txn_cost_per_object_in_commit * 5,
)
}
};
Expand Down Expand Up @@ -1011,6 +1169,7 @@ mod object_cost_tests {
None,
None,
0,
0,
);

let accumulated_debts = shared_object_congestion_tracker.accumulated_debts();
Expand All @@ -1032,6 +1191,7 @@ mod object_cost_tests {
Some(1000),
Some(2),
1000,
0,
);

// Create a transaction using all three objects
Expand Down
1 change: 1 addition & 0 deletions crates/sui-core/src/unit_tests/congestion_control_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ async fn test_congestion_control_execution_cancellation() {
Some(1000), // Not used.
None, // Not used.
0, // Disable overage.
0,
))
});

Expand Down
1 change: 1 addition & 0 deletions crates/sui-open-rpc/spec/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,7 @@
"address_to_u256_cost_base": {
"u64": "52"
},
"allowed_txn_cost_overage_burst_per_object_in_commit": null,
"base_tx_cost_fixed": {
"u64": "2000"
},
Expand Down
12 changes: 10 additions & 2 deletions crates/sui-protocol-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,10 +1286,16 @@ pub struct ProtocolConfig {
/// Transactions will be cancelled after this many rounds.
max_deferral_rounds_for_congestion_control: Option<u64>,

/// If >0, congestion control will allow up to one transaction per object to exceed
/// the configured maximum accumulated cost by the given amount.
/// If >0, congestion control will allow the configured maximum accumulated cost per object
/// to be exceeded by at most the given amount. Only one limit-exceeding transaction per
/// object will be allowed, unless bursting is configured below.
max_txn_cost_overage_per_object_in_commit: Option<u64>,

/// If >0, congestion control will allow transactions in total cost equaling the
/// configured amount to exceed the configured maximum accumulated cost per object.
/// As above, up to one transaction per object exceeding the burst limit will be allowed.
allowed_txn_cost_overage_burst_per_object_in_commit: Option<u64>,

/// Minimum interval of commit timestamps between consecutive checkpoints.
min_checkpoint_interval_ms: Option<u64>,

Expand Down Expand Up @@ -2226,6 +2232,8 @@ impl ProtocolConfig {

max_txn_cost_overage_per_object_in_commit: None,

allowed_txn_cost_overage_burst_per_object_in_commit: None,

min_checkpoint_interval_ms: None,

checkpoint_summary_version_specific_data: None,
Expand Down

0 comments on commit 1013f93

Please sign in to comment.