diff --git a/api/src/context.rs b/api/src/context.rs index 73b3c31b11d91..939276891d81c 100644 --- a/api/src/context.rs +++ b/api/src/context.rs @@ -16,7 +16,7 @@ use aptos_api_types::{ AptosErrorCode, AsConverter, BcsBlock, GasEstimation, LedgerInfo, ResourceGroup, TransactionOnChainData, }; -use aptos_config::config::{NodeConfig, RoleType}; +use aptos_config::config::{GasEstimationConfig, NodeConfig, RoleType}; use aptos_crypto::HashValue; use aptos_gas_schedule::{AptosGasParameters, FromOnChainGasSchedule}; use aptos_logger::{error, info, Schema}; @@ -29,7 +29,6 @@ use aptos_types::{ access_path::{AccessPath, Path}, account_address::AccountAddress, account_config::{AccountResource, NewBlockEvent}, - block_executor::config::BlockExecutorConfigFromOnchain, chain_id::ChainId, contract_event::EventWithVersion, event::EventKey, @@ -42,7 +41,9 @@ use aptos_types::{ TStateView, }, transaction::{ - block_epilogue::BlockEndInfo, SignedTransaction, Transaction, TransactionWithProof, Version, + block_epilogue::BlockEndInfo, + use_case::{UseCaseAwareTransaction, UseCaseKey}, + SignedTransaction, Transaction, TransactionWithProof, Version, }, }; use futures::{channel::oneshot, SinkExt}; @@ -124,8 +125,7 @@ impl Context { })), gas_limit_cache: Arc::new(RwLock::new(GasLimitCache { last_updated_epoch: None, - block_executor_onchain_config: OnChainExecutionConfig::default_if_missing() - .block_executor_onchain_config(), + execution_onchain_config: OnChainExecutionConfig::default_if_missing(), })), view_function_stats, simulate_txn_stats, @@ -1000,9 +1000,10 @@ impl Context { start_version: Version, limit: u64, ledger_version: Version, - ) -> Result<(Vec<(u64, u64)>, Vec)> { + count_majority_use_case: bool, + ) -> Result<(Vec<(u64, u64)>, Vec, Option)> { if start_version > ledger_version || limit == 0 { - return Ok((vec![], vec![])); + return Ok((vec![], vec![], None)); } // This is just an estimation, so we can just skip over errors @@ -1014,11 +1015,16 @@ impl Context { let mut gas_prices = Vec::new(); let mut block_end_infos = Vec::new(); + let mut count_by_use_case = HashMap::new(); for (txn, info) in txns.zip(infos) { match txn.as_ref() { Ok(Transaction::UserTransaction(txn)) => { if let Ok(info) = info.as_ref() { gas_prices.push((txn.gas_unit_price(), info.gas_used())); + if count_majority_use_case { + let use_case_key = txn.parse_use_case(); + *count_by_use_case.entry(use_case_key).or_insert(0) += 1; + } } }, Ok(Transaction::BlockEpilogue(txn)) => { @@ -1030,7 +1036,80 @@ impl Context { } } - Ok((gas_prices, block_end_infos)) + let majority_use_case_fraction = if count_majority_use_case { + count_by_use_case.iter().max_by_key(|(_, v)| *v).and_then( + |(max_use_case, max_value)| { + if let UseCaseKey::ContractAddress(_) = max_use_case { + Some(*max_value as f32 / count_by_use_case.values().sum::() as f32) + } else { + None + } + }, + ) + } else { + None + }; + Ok((gas_prices, block_end_infos, majority_use_case_fraction)) + } + + fn block_min_inclusion_price( + &self, + ledger_info: &LedgerInfo, + first: Version, + last: Version, + gas_estimation_config: &GasEstimationConfig, + execution_config: &OnChainExecutionConfig, + ) -> Option { + let user_use_case_spread_factor = if gas_estimation_config.incorporate_reordering_effects { + execution_config + .transaction_shuffler_type() + .user_use_case_spread_factor() + } else { + None + }; + + match self.get_gas_prices_and_used( + first, + last - first, + ledger_info.ledger_version.0, + user_use_case_spread_factor.is_some(), + ) { + Ok((prices_and_used, block_end_infos, majority_use_case_fraction)) => { + let is_full_block = + if majority_use_case_fraction.map_or(false, |fraction| fraction > 0.5) { + // If majority use case is above half of transactions, UseCaseAware block reordering + // will allow other transactions to get in the block (AIP-68) + false + } else if prices_and_used.len() >= gas_estimation_config.full_block_txns { + true + } else if !block_end_infos.is_empty() { + assert_eq!(1, block_end_infos.len()); + block_end_infos.first().unwrap().limit_reached() + } else if let Some(block_gas_limit) = + execution_config.block_gas_limit_type().block_gas_limit() + { + let gas_used = prices_and_used.iter().map(|(_, used)| *used).sum::(); + gas_used >= block_gas_limit + } else { + false + }; + + if is_full_block { + Some( + self.next_bucket( + prices_and_used + .iter() + .map(|(price, _)| *price) + .min() + .unwrap(), + ), + ) + } else { + None + } + }, + Err(_) => None, + } } pub fn estimate_gas_price( @@ -1039,7 +1118,7 @@ impl Context { ) -> Result { let config = &self.node_config.api.gas_estimation; let min_gas_unit_price = self.min_gas_unit_price(ledger_info)?; - let block_config = self.block_executor_onchain_config(ledger_info)?; + let execution_config = self.execution_onchain_config(ledger_info)?; if !config.enabled { return Ok(self.default_gas_estimation(min_gas_unit_price)); } @@ -1120,40 +1199,9 @@ impl Context { let mut min_inclusion_prices = vec![]; // TODO: if multiple calls to db is a perf issue, combine into a single call and then split for (first, last) in blocks { - let min_inclusion_price = match self.get_gas_prices_and_used( - first, - last - first, - ledger_info.ledger_version.0, - ) { - Ok((prices_and_used, block_end_infos)) => { - let is_full_block = if prices_and_used.len() >= config.full_block_txns { - true - } else if !block_end_infos.is_empty() { - assert_eq!(1, block_end_infos.len()); - block_end_infos.first().unwrap().limit_reached() - } else if let Some(block_gas_limit) = - block_config.block_gas_limit_type.block_gas_limit() - { - let gas_used = prices_and_used.iter().map(|(_, used)| *used).sum::(); - gas_used >= block_gas_limit - } else { - false - }; - - if is_full_block { - self.next_bucket( - prices_and_used - .iter() - .map(|(price, _)| *price) - .min() - .unwrap(), - ) - } else { - min_gas_unit_price - } - }, - Err(_) => min_gas_unit_price, - }; + let min_inclusion_price = self + .block_min_inclusion_price(ledger_info, first, last, config, &execution_config) + .unwrap_or(min_gas_unit_price); min_inclusion_prices.push(min_inclusion_price); cache .min_inclusion_prices @@ -1321,16 +1369,16 @@ impl Context { } } - pub fn block_executor_onchain_config( + pub fn execution_onchain_config( &self, ledger_info: &LedgerInfo, - ) -> Result { + ) -> Result { // If it's the same epoch, use the cached results { let cache = self.gas_limit_cache.read().unwrap(); if let Some(ref last_updated_epoch) = cache.last_updated_epoch { if *last_updated_epoch == ledger_info.epoch.0 { - return Ok(cache.block_executor_onchain_config.clone()); + return Ok(cache.execution_onchain_config.clone()); } } } @@ -1341,7 +1389,7 @@ impl Context { // If a different thread updated the cache, we can exit early if let Some(ref last_updated_epoch) = cache.last_updated_epoch { if *last_updated_epoch == ledger_info.epoch.0 { - return Ok(cache.block_executor_onchain_config.clone()); + return Ok(cache.execution_onchain_config.clone()); } } @@ -1353,14 +1401,13 @@ impl Context { E::internal_with_code(e, AptosErrorCode::InternalError, ledger_info) })?; - let block_executor_onchain_config = OnChainExecutionConfig::fetch_config(&state_view) - .unwrap_or_else(OnChainExecutionConfig::default_if_missing) - .block_executor_onchain_config(); + let execution_onchain_config = OnChainExecutionConfig::fetch_config(&state_view) + .unwrap_or_else(OnChainExecutionConfig::default_if_missing); // Update the cache - cache.block_executor_onchain_config = block_executor_onchain_config.clone(); + cache.execution_onchain_config = execution_onchain_config.clone(); cache.last_updated_epoch = Some(ledger_info.epoch.0); - Ok(block_executor_onchain_config) + Ok(execution_onchain_config) } } @@ -1420,7 +1467,7 @@ pub struct GasEstimationCache { pub struct GasLimitCache { last_updated_epoch: Option, - block_executor_onchain_config: BlockExecutorConfigFromOnchain, + execution_onchain_config: OnChainExecutionConfig, } /// This function just calls tokio::task::spawn_blocking with the given closure and in diff --git a/config/src/config/gas_estimation_config.rs b/config/src/config/gas_estimation_config.rs index 763ac1b947486..00d943e9c1668 100644 --- a/config/src/config/gas_estimation_config.rs +++ b/config/src/config/gas_estimation_config.rs @@ -32,6 +32,8 @@ pub struct GasEstimationConfig { pub aggressive_block_history: usize, /// Time after write when previous value is returned without recomputing pub cache_expiration_ms: u64, + /// Whether to account which TransactionShufflerType is used onchain, and how it affects gas estimation + pub incorporate_reordering_effects: bool, } impl Default for GasEstimationConfig { @@ -44,6 +46,7 @@ impl Default for GasEstimationConfig { market_block_history: 30, aggressive_block_history: 120, cache_expiration_ms: 500, + incorporate_reordering_effects: true, } } } diff --git a/types/src/on_chain_config/execution_config.rs b/types/src/on_chain_config/execution_config.rs index 97ce9773022af..3b1c753edcefa 100644 --- a/types/src/on_chain_config/execution_config.rs +++ b/types/src/on_chain_config/execution_config.rs @@ -168,6 +168,19 @@ impl TransactionShufflerType { user_use_case_spread_factor: 4, } } + + pub fn user_use_case_spread_factor(&self) -> Option { + match self { + TransactionShufflerType::NoShuffling + | TransactionShufflerType::DeprecatedSenderAwareV1(_) + | TransactionShufflerType::SenderAwareV2(_) + | TransactionShufflerType::DeprecatedFairness { .. } => None, + TransactionShufflerType::UseCaseAware { + user_use_case_spread_factor, + .. + } => Some(*user_use_case_spread_factor), + } + } } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]