diff --git a/crates/sui-core/src/authority/authority_per_epoch_store.rs b/crates/sui-core/src/authority/authority_per_epoch_store.rs index e50f8cc713743..4a5e3322bd7b2 100644 --- a/crates/sui-core/src/authority/authority_per_epoch_store.rs +++ b/crates/sui-core/src/authority/authority_per_epoch_store.rs @@ -84,7 +84,7 @@ use typed_store::{ use super::authority_store_tables::ENV_VAR_LOCKS_BLOCK_CACHE_SIZE; use super::epoch_start_configuration::EpochStartConfigTrait; use super::shared_object_congestion_tracker::{ - CongestionPerObjectDebt, SharedObjectCongestionTracker, + CongestionPerObjectDebt, Debt, SharedObjectCongestionTracker, }; use super::transaction_deferral::{transaction_deferral_within_limit, DeferralKey, DeferralReason}; use crate::authority::epoch_start_configuration::{EpochFlag, EpochStartConfiguration}; @@ -2808,21 +2808,26 @@ impl AuthorityPerEpochStore { // We track transaction execution cost separately for regular transactions and transactions using randomness, since // they will be in different PendingCheckpoints. - let tables = self.tables()?; let shared_object_congestion_tracker = SharedObjectCongestionTracker::from_protocol_config( - &tables, + self.consensus_quarantine.read().load_initial_object_debts( + self, + consensus_commit_info.round, + false, + &sequenced_transactions, + )?, self.protocol_config(), - consensus_commit_info.round, false, - &sequenced_transactions, )?; let shared_object_using_randomness_congestion_tracker = SharedObjectCongestionTracker::from_protocol_config( - &tables, + self.consensus_quarantine.read().load_initial_object_debts( + self, + consensus_commit_info.round, + true, + &sequenced_transactions, + )?, self.protocol_config(), - consensus_commit_info.round, true, - &sequenced_randomness_transactions, )?; // We always order transactions using randomness last. @@ -4199,6 +4204,8 @@ impl AuthorityPerEpochStore { mod quarantine { use mysten_common::fatal; + use crate::authority::shared_object_congestion_tracker::Debt; + use super::*; /// ConsensusOutputQuarantine holds outputs of consensus processing in memory until the checkpoints @@ -4598,19 +4605,26 @@ mod quarantine { epoch_store: &AuthorityPerEpochStore, current_round: Round, for_randomness: bool, - per_commit_budget: u64, transactions: &[VerifiedSequencedConsensusTransaction], - ) -> SuiResult> { + ) -> SuiResult> { + let protocol_config = epoch_store.protocol_config(); let tables = epoch_store.tables()?; - let (hash_table, db_table) = if for_randomness { + let default_per_commit_budget = protocol_config + .max_accumulated_txn_cost_per_object_in_mysticeti_commit_as_option() + .unwrap_or(0); + let (hash_table, db_table, per_commit_budget) = if for_randomness { ( &self.congestion_control_randomness_object_debts, &tables.congestion_control_randomness_object_debts, + protocol_config + .max_accumulated_randomness_txn_cost_per_object_in_mysticeti_commit_as_option() + .unwrap_or(default_per_commit_budget), ) } else { ( &self.congestion_control_object_debts, &tables.congestion_control_object_debts, + default_per_commit_budget, ) }; let shared_input_object_ids: BTreeSet<_> = transactions @@ -4652,19 +4666,15 @@ mod quarantine { Ok(results .into_iter() - .zip(shared_input_object_ids.into_iter()) - .flat_map(move |(debt, object_id)| { - if let Some((round, debt)) = debt { - Some(( - object_id, - // Stored debts already account for the budget of the round in which - // they were accumulated. Application of budget from future rounds to - // the debt is handled here. - debt.saturating_sub(per_commit_budget * (current_round - round - 1)), - )) - } else { - None - } + .zip(shared_input_object_ids) + .filter_map(|(debt, object_id)| debt.map(|debt| (debt, object_id))) + .map(move |((round, debt), object_id)| { + // Stored debts already account for the budget of the round in which + // they were accumulated. Application of budget from future rounds to + // the debt is handled here. + assert!(current_round > round); + let num_rounds = current_round - round - 1; + (object_id, debt.dec_by(per_commit_budget * num_rounds)) })) } } @@ -4705,8 +4715,8 @@ pub(crate) struct ConsensusCommitOutput { active_jwks: BTreeSet<(u64, (JwkId, JWK))>, // congestion control state - congestion_control_object_debts: Vec<(ObjectID, u64)>, - congestion_control_randomness_object_debts: Vec<(ObjectID, u64)>, + congestion_control_object_debts: Vec<(ObjectID, Debt)>, + congestion_control_randomness_object_debts: Vec<(ObjectID, Debt)>, } impl ConsensusCommitOutput { @@ -4836,13 +4846,13 @@ impl ConsensusCommitOutput { self.active_jwks.insert((round, key)); } - fn set_congestion_control_object_debts(&mut self, object_debts: Vec<(ObjectID, u64)>) { + fn set_congestion_control_object_debts(&mut self, object_debts: Vec<(ObjectID, Debt)>) { self.congestion_control_object_debts = object_debts; } fn set_congestion_control_randomness_object_debts( &mut self, - object_debts: Vec<(ObjectID, u64)>, + object_debts: Vec<(ObjectID, Debt)>, ) { self.congestion_control_randomness_object_debts = object_debts; } diff --git a/crates/sui-core/src/authority/shared_object_congestion_tracker.rs b/crates/sui-core/src/authority/shared_object_congestion_tracker.rs index e531d11605419..ec66baad1761f 100644 --- a/crates/sui-core/src/authority/shared_object_congestion_tracker.rs +++ b/crates/sui-core/src/authority/shared_object_congestion_tracker.rs @@ -1,9 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use super::authority_per_epoch_store::AuthorityEpochTables; use crate::authority::transaction_deferral::DeferralKey; -use crate::consensus_handler::VerifiedSequencedConsensusTransaction; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig}; @@ -38,15 +36,17 @@ pub struct SharedObjectCongestionTracker { impl SharedObjectCongestionTracker { pub fn new( - initial_object_debts: impl IntoIterator, + initial_object_debts: impl IntoIterator, mode: PerObjectCongestionControlMode, max_accumulated_txn_cost_per_object_in_commit: Option, gas_budget_based_txn_cost_cap_factor: Option, gas_budget_based_txn_cost_absolute_cap_commit_count: Option, max_txn_cost_overage_per_object_in_commit: u64, ) -> Self { - let object_execution_cost: HashMap = - initial_object_debts.into_iter().collect(); + let object_execution_cost: HashMap = initial_object_debts + .into_iter() + .map(|(obj_id, debt)| (obj_id, debt.0)) + .collect(); let max_accumulated_txn_cost_per_object_in_commit = if mode == PerObjectCongestionControlMode::None { 0 @@ -79,21 +79,14 @@ impl SharedObjectCongestionTracker { } pub fn from_protocol_config( - tables: &AuthorityEpochTables, + initial_object_debts: impl IntoIterator, protocol_config: &ProtocolConfig, - round: Round, for_randomness: bool, - transactions: &[VerifiedSequencedConsensusTransaction], ) -> SuiResult { let max_accumulated_txn_cost_per_object_in_commit = protocol_config.max_accumulated_txn_cost_per_object_in_mysticeti_commit_as_option(); Ok(Self::new( - tables.load_initial_object_debts( - round, - for_randomness, - protocol_config, - transactions, - )?, + initial_object_debts, protocol_config.per_object_congestion_control_mode(), if for_randomness { protocol_config @@ -224,7 +217,7 @@ impl SharedObjectCongestionTracker { // Returns accumulated debts for objects whose budgets have been exceeded over the course // of the commit. Consumes the tracker object, since this should only be called once after // all tx have been processed. - pub fn accumulated_debts(self) -> Vec<(ObjectID, u64)> { + pub fn accumulated_debts(self) -> Vec<(ObjectID, Debt)> { if self.max_txn_cost_overage_per_object_in_commit == 0 { return vec![]; // early-exit if overage is not allowed } @@ -240,6 +233,7 @@ impl SharedObjectCongestionTracker { None } }) + .map(|(obj_id, debt)| (obj_id, Debt(debt))) .collect() } @@ -284,14 +278,23 @@ pub enum CongestionPerObjectDebt { V1(Round, u64), } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Debt(pub u64); + +impl Debt { + pub fn dec_by(self, amount: u64) -> Self { + Self(self.0.saturating_sub(amount)) + } +} + impl CongestionPerObjectDebt { - pub fn new(round: Round, debt: u64) -> Self { - Self::V1(round, debt) + pub fn new(round: Round, debt: Debt) -> Self { + Self::V1(round, debt.0) } - pub fn into_v1(self) -> (Round, u64) { + pub fn into_v1(self) -> (Round, Debt) { match self { - Self::V1(round, debt) => (round, debt), + Self::V1(round, debt) => (round, Debt(debt)), } } } @@ -326,7 +329,7 @@ mod object_cost_tests { let object_id_2 = ObjectID::random(); let shared_object_congestion_tracker = SharedObjectCongestionTracker::new( - [(object_id_0, 5), (object_id_1, 10)], + [(object_id_0, Debt(5)), (object_id_1, Debt(10))], PerObjectCongestionControlMode::TotalGasBudget, Some(0), // not part of this test None, @@ -477,7 +480,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 10), (shared_obj_1, 1)], + [(shared_obj_0, Debt(10)), (shared_obj_1, Debt(1))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -491,7 +494,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 2), (shared_obj_1, 1)], + [(shared_obj_0, Debt(2)), (shared_obj_1, Debt(1))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -505,7 +508,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 10), (shared_obj_1, 1)], + [(shared_obj_0, Debt(10)), (shared_obj_1, Debt(1))], 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. @@ -694,7 +697,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 102), (shared_obj_1, 90)], + [(shared_obj_0, Debt(102)), (shared_obj_1, Debt(90))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -708,7 +711,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 3), (shared_obj_1, 2)], + [(shared_obj_0, Debt(3)), (shared_obj_1, Debt(2))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -722,7 +725,7 @@ mod object_cost_tests { // object 0: | // object 1: | SharedObjectCongestionTracker::new( - [(shared_obj_0, 100), (shared_obj_1, 90)], + [(shared_obj_0, Debt(100)), (shared_obj_1, Debt(90))], 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. @@ -788,7 +791,7 @@ mod object_cost_tests { let cap_factor = Some(1); let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new( - [(object_id_0, 5), (object_id_1, 10)], + [(object_id_0, Debt(5)), (object_id_1, Debt(10))], mode, Some(0), // not part of this test cap_factor, @@ -803,7 +806,7 @@ mod object_cost_tests { assert_eq!( shared_object_congestion_tracker, SharedObjectCongestionTracker::new( - [(object_id_0, 5), (object_id_1, 10)], + [(object_id_0, Debt(5)), (object_id_1, Debt(10))], mode, Some(0), // not part of this test cap_factor, @@ -825,7 +828,10 @@ mod object_cost_tests { assert_eq!( shared_object_congestion_tracker, SharedObjectCongestionTracker::new( - [(object_id_0, expected_object_0_cost), (object_id_1, 10)], + [ + (object_id_0, Debt(expected_object_0_cost)), + (object_id_1, Debt(10)) + ], mode, Some(0), // not part of this test cap_factor, @@ -858,9 +864,9 @@ mod object_cost_tests { shared_object_congestion_tracker, SharedObjectCongestionTracker::new( [ - (object_id_0, expected_object_cost), - (object_id_1, expected_object_cost), - (object_id_2, expected_object_cost) + (object_id_0, Debt(expected_object_cost)), + (object_id_1, Debt(expected_object_cost)), + (object_id_2, Debt(expected_object_cost)) ], mode, Some(0), // not part of this test @@ -895,9 +901,9 @@ mod object_cost_tests { shared_object_congestion_tracker, SharedObjectCongestionTracker::new( [ - (object_id_0, expected_object_cost), - (object_id_1, expected_object_cost), - (object_id_2, expected_object_cost) + (object_id_0, Debt(expected_object_cost)), + (object_id_1, Debt(expected_object_cost)), + (object_id_2, Debt(expected_object_cost)) ], mode, Some(0), // not part of this test @@ -943,7 +949,7 @@ mod object_cost_tests { PerObjectCongestionControlMode::TotalGasBudget => { // Starting with two objects with accumulated cost 80. SharedObjectCongestionTracker::new( - [(shared_obj_0, 80), (shared_obj_1, 80)], + [(shared_obj_0, Debt(80)), (shared_obj_1, Debt(80))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -954,7 +960,7 @@ mod object_cost_tests { PerObjectCongestionControlMode::TotalGasBudgetWithCap => { // Starting with two objects with accumulated cost 80. SharedObjectCongestionTracker::new( - [(shared_obj_0, 80), (shared_obj_1, 80)], + [(shared_obj_0, Debt(80)), (shared_obj_1, Debt(80))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), Some(45), @@ -965,7 +971,7 @@ mod object_cost_tests { PerObjectCongestionControlMode::TotalTxCount => { // Starting with two objects with accumulated tx count 2. SharedObjectCongestionTracker::new( - [(shared_obj_0, 2), (shared_obj_1, 2)], + [(shared_obj_0, Debt(2)), (shared_obj_1, Debt(2))], mode, Some(max_accumulated_txn_cost_per_object_in_commit), None, @@ -987,13 +993,13 @@ mod object_cost_tests { match mode { PerObjectCongestionControlMode::None => unreachable!(), PerObjectCongestionControlMode::TotalGasBudget => { - assert_eq!(accumulated_debts[0], (shared_obj_0, 90)); // init 80 + cost 100 - budget 90 = 90 + assert_eq!(accumulated_debts[0], (shared_obj_0, Debt(90))); // init 80 + cost 100 - budget 90 = 90 } PerObjectCongestionControlMode::TotalGasBudgetWithCap => { - assert_eq!(accumulated_debts[0], (shared_obj_0, 80)); // init 80 + capped cost 90 - budget 90 = 80 + assert_eq!(accumulated_debts[0], (shared_obj_0, Debt(80))); // init 80 + capped cost 90 - budget 90 = 80 } PerObjectCongestionControlMode::TotalTxCount => { - assert_eq!(accumulated_debts[0], (shared_obj_0, 1)); // init 2 + 1 tx - budget 2 = 1 + assert_eq!(accumulated_debts[0], (shared_obj_0, Debt(1))); // init 2 + 1 tx - budget 2 = 1 } } } @@ -1005,7 +1011,11 @@ mod object_cost_tests { let object_id_2 = ObjectID::random(); let shared_object_congestion_tracker = SharedObjectCongestionTracker::new( - [(object_id_0, 5), (object_id_1, 10), (object_id_2, 100)], + [ + (object_id_0, Debt(5)), + (object_id_1, Debt(10)), + (object_id_2, Debt(100)), + ], PerObjectCongestionControlMode::TotalGasBudget, Some(100), None, @@ -1026,7 +1036,11 @@ mod object_cost_tests { let tx_gas_budget = 2000; let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new( - [(object_id_0, 5), (object_id_1, 10), (object_id_2, 100)], + [ + (object_id_0, Debt(5)), + (object_id_1, Debt(10)), + (object_id_2, Debt(100)), + ], PerObjectCongestionControlMode::TotalGasBudgetWithCap, Some(100), Some(1000), @@ -1057,6 +1071,6 @@ mod object_cost_tests { // Verify accumulated debts still uses the per-commit budget to decrement. let accumulated_debts = shared_object_congestion_tracker.accumulated_debts(); assert_eq!(accumulated_debts.len(), 1); - assert_eq!(accumulated_debts[0], (object_id_2, 200)); + assert_eq!(accumulated_debts[0], (object_id_2, Debt(200))); } } diff --git a/crates/sui-core/src/unit_tests/congestion_control_tests.rs b/crates/sui-core/src/unit_tests/congestion_control_tests.rs index cecc20130421b..72d1845beb3cc 100644 --- a/crates/sui-core/src/unit_tests/congestion_control_tests.rs +++ b/crates/sui-core/src/unit_tests/congestion_control_tests.rs @@ -2,7 +2,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::authority::shared_object_congestion_tracker::SharedObjectCongestionTracker; +use crate::authority::shared_object_congestion_tracker::{Debt, SharedObjectCongestionTracker}; use crate::{ authority::{ authority_tests::{ @@ -292,7 +292,7 @@ async fn test_congestion_control_execution_cancellation() { // Initialize shared object queue so that any transaction touches shared_object_1 should result in congestion and cancellation. register_fail_point_arg("initial_congestion_tracker", move || { Some(SharedObjectCongestionTracker::new( - [(shared_object_1.0, 10)], + [(shared_object_1.0, Debt(10))], PerObjectCongestionControlMode::TotalGasBudget, Some( test_setup