From a1fb747bfaa928305e3e62fe65d1e3e670cc5230 Mon Sep 17 00:00:00 2001 From: Theo Butler Date: Thu, 14 Sep 2023 12:03:06 -0600 Subject: [PATCH] fix: remove application of user preferences in indexer-selection This remove the interpolation between utility curves for certain selection factors. This feature provided little value to users while also making it difficult to debug & adjust ISA weights based on user QoS issues. --- graph-gateway/src/auth.rs | 12 ++----- graph-gateway/src/client_query.rs | 15 ++++----- graph-gateway/src/subgraph_studio.rs | 33 ------------------- indexer-selection/bin/sim.rs | 12 +++---- indexer-selection/src/fee.rs | 26 ++++++++------- indexer-selection/src/indexing.rs | 9 +---- indexer-selection/src/lib.rs | 49 +--------------------------- indexer-selection/src/performance.rs | 10 +++--- indexer-selection/src/score.rs | 43 +++++++++++++++--------- indexer-selection/src/test.rs | 7 +++- 10 files changed, 66 insertions(+), 150 deletions(-) diff --git a/graph-gateway/src/auth.rs b/graph-gateway/src/auth.rs index 63f9ca26..e71ce4f8 100644 --- a/graph-gateway/src/auth.rs +++ b/graph-gateway/src/auth.rs @@ -15,7 +15,7 @@ use prelude::USD; use tokio::sync::RwLock; use toolshed::thegraph::{DeploymentId, SubgraphId}; -use crate::subgraph_studio::{APIKey, IndexerPreferences, QueryStatus}; +use crate::subgraph_studio::{APIKey, QueryStatus}; use crate::subscriptions::Subscription; use crate::topology::Deployment; @@ -30,7 +30,6 @@ pub struct AuthHandler { #[derive(Debug)] pub struct UserSettings { pub budget: Option, - pub indexer_preferences: IndexerPreferences, } pub enum AuthToken { @@ -219,14 +218,7 @@ impl AuthHandler { AuthToken::ApiKey(api_key) => api_key.max_budget, _ => None, }; - let indexer_preferences = match token { - AuthToken::ApiKey(api_key) => api_key.indexer_preferences.clone(), - AuthToken::Ticket(_, _) => IndexerPreferences::default(), - }; - UserSettings { - budget, - indexer_preferences, - } + UserSettings { budget } } } diff --git a/graph-gateway/src/client_query.rs b/graph-gateway/src/client_query.rs index 2e16bfd9..1f0ca04f 100644 --- a/graph-gateway/src/client_query.rs +++ b/graph-gateway/src/client_query.rs @@ -542,16 +542,13 @@ async fn handle_client_query_inner( budget_grt = budget.as_f64() as f32, ); - let mut utility_params = UtilityParameters::new( + let mut utility_params = UtilityParameters { budget, - block_requirements, - 0, // 170cbcf3-db7f-404a-be13-2022d9142677 - block_cache.block_rate_hz, - user_settings.indexer_preferences.performance, - user_settings.indexer_preferences.data_freshness, - user_settings.indexer_preferences.economic_security, - user_settings.indexer_preferences.price_efficiency, - ); + requirements: block_requirements, + // 170cbcf3-db7f-404a-be13-2022d9142677 + latest_block: 0, + block_rate_hz: block_cache.block_rate_hz, + }; let mut rng = SmallRng::from_entropy(); diff --git a/graph-gateway/src/subgraph_studio.rs b/graph-gateway/src/subgraph_studio.rs index ff53cb20..c9e08413 100644 --- a/graph-gateway/src/subgraph_studio.rs +++ b/graph-gateway/src/subgraph_studio.rs @@ -20,7 +20,6 @@ pub struct APIKey { pub deployments: Vec, pub subgraphs: Vec, pub domains: Vec, - pub indexer_preferences: IndexerPreferences, } #[derive(Clone, Copy, Debug, Default, Deserialize)] @@ -32,15 +31,6 @@ pub enum QueryStatus { ServiceShutoff, } -#[derive(Clone, Debug, Default)] -pub struct IndexerPreferences { - pub freshness_requirements: f64, - pub performance: f64, - pub data_freshness: f64, - pub economic_security: f64, - pub price_efficiency: f64, -} - pub fn api_keys( client: reqwest::Client, mut url: Url, @@ -89,20 +79,6 @@ impl Client { .api_keys .into_iter() .filter_map(|api_key| { - let mut indexer_preferences = IndexerPreferences::default(); - for preference in api_key.indexer_preferences { - match preference.name.as_str() { - "Fastest speed" => indexer_preferences.performance = preference.weight, - "Lowest price" => indexer_preferences.price_efficiency = preference.weight, - "Data freshness" => indexer_preferences.data_freshness = preference.weight, - "Economic security" => { - indexer_preferences.economic_security = preference.weight - } - unexpected_indexer_preference_name => { - tracing::warn!(%unexpected_indexer_preference_name) - } - } - } let api_key = APIKey { id: api_key.id, key: api_key.key, @@ -126,7 +102,6 @@ impl Client { .into_iter() .map(|domain| domain.domain) .collect(), - indexer_preferences, }; Some((api_key.key.clone(), Arc::new(api_key))) }) @@ -152,8 +127,6 @@ struct GatewayApiKey { query_status: QueryStatus, max_budget: Option, #[serde(default)] - indexer_preferences: Vec, - #[serde(default)] subgraphs: Vec, #[serde(default)] deployments: Vec, @@ -161,12 +134,6 @@ struct GatewayApiKey { domains: Vec, } -#[derive(Deserialize)] -struct GatewayIndexerPreference { - name: String, - weight: f64, -} - #[derive(Deserialize)] struct GatewaySubgraph { network_subgraph_id: String, diff --git a/indexer-selection/bin/sim.rs b/indexer-selection/bin/sim.rs index 70b9fdc7..375c27c6 100644 --- a/indexer-selection/bin/sim.rs +++ b/indexer-selection/bin/sim.rs @@ -42,16 +42,12 @@ async fn main() -> Result<()> { gen_blocks(&(0..last_block).collect::>()) }; let latest_block = blocks.last().unwrap().number; - let params = UtilityParameters::new( + let params = UtilityParameters { budget, - freshness_requirements, + requirements: freshness_requirements, latest_block, - 0.1, - 0.0, - 0.0, - 0.0, - 0.0, - ); + block_rate_hz: 0.1, + }; println!("label,indexer,detail,selections,fees"); eprintln!("| selection limit | total fees (GRT) | avg. latency (ms) | avg. blocks behind | avg. indexers selected | avg. selection time (ms) |"); diff --git a/indexer-selection/src/fee.rs b/indexer-selection/src/fee.rs index 4fcf9770..a69513cd 100644 --- a/indexer-selection/src/fee.rs +++ b/indexer-selection/src/fee.rs @@ -19,6 +19,9 @@ use crate::{utility::UtilityFactor, Context, IndexerError, InputError, Selection /// (5_f64.sqrt() - 1.0) / 2.0 const S: f64 = 0.6180339887498949; +// 7a3da342-c049-4ab0-8058-91880491b442 +const WEIGHT: f64 = 0.5; + /// Constraints for the utility function `u(fee)`: /// - u(0) = 1, u(budget + 1 GRTWei) = 0, and u is continuous within this input range. We want to /// avoid a discontinuity at the budget where a 1 GRTWei difference in the fee suddenly sends @@ -70,9 +73,9 @@ const S: f64 = 0.6180339887498949; /// - It seems that assuming mostly rational Indexers and a medium sized pool, /// the Consumer may expect to pay ~55-75% of the maximum budget. -/// https://www.desmos.com/calculator/wnffyb9edh -/// 3534cc5a-f562-48ce-ac7a-88737c80698b -pub fn fee_utility(weight: f64, fee: &GRT, budget: &GRT) -> UtilityFactor { +// https://www.desmos.com/calculator/wnffyb9edh +// 7a3da342-c049-4ab0-8058-91880491b442 +pub fn fee_utility(fee: &GRT, budget: &GRT) -> UtilityFactor { // Any fee over budget has zero utility. if *fee > *budget { return UtilityFactor::one(0.0); @@ -83,7 +86,10 @@ pub fn fee_utility(weight: f64, fee: &GRT, budget: &GRT) -> UtilityFactor { // Set minimum utility, since small negative utility can result from loss of precision when the // fee approaches the budget. utility = utility.max(1e-18); - UtilityFactor { utility, weight } + UtilityFactor { + utility, + weight: 0.5, + } } /// Indexers set their fees using cost models. However, indexers currently take a "lazy" approach to @@ -95,8 +101,8 @@ pub fn fee_utility(weight: f64, fee: &GRT, budget: &GRT) -> UtilityFactor { /// function to `fee * utility`. Solving for the correct revenue maximizing value is complex and /// recursive (since the revenue maximizing fee depends on the utility of all other indexers which /// itself depends on their revenue maximizing fee... ad infinitum). -fn min_optimal_fee(weight: f64, budget: &GRT) -> GRT { - let w = weight; +fn min_optimal_fee(budget: &GRT) -> GRT { + let w = WEIGHT; let mut min_rate = (4.0 * S.powi(2) * w + w.powi(2) - 2.0 * w + 1.0).sqrt(); min_rate = (min_rate - 2.0 * S.powi(2) - w + 1.0) / (2.0 * S); *budget * GRT::try_from(min_rate).unwrap() @@ -105,7 +111,6 @@ fn min_optimal_fee(weight: f64, budget: &GRT) -> GRT { pub fn indexer_fee( cost_model: &Option>, context: &mut Context<'_>, - weight: f64, budget: &GRT, max_indexers: u8, ) -> Result { @@ -134,7 +139,7 @@ pub fn indexer_fee( } let budget = *budget / GRT::try_from(max_indexers).unwrap(); - let min_optimal_fee = min_optimal_fee(weight, &budget); + let min_optimal_fee = min_optimal_fee(&budget); // If their fee is less than the min optimal, lerp between them so that // indexers are rewarded for being closer. if fee < min_optimal_fee { @@ -158,11 +163,10 @@ mod test { let mut context = Context::new(BASIC_QUERY, "").unwrap(); // Expected values based on https://www.desmos.com/calculator/kxd4kpjxi5 let tests = [(0.01, 0.0), (0.02, 0.27304), (0.1, 0.50615), (1.0, 0.55769)]; - let weight = 0.5; for (budget, expected_utility) in tests { let budget = budget.to_string().parse::().unwrap(); - let fee = indexer_fee(&cost_model, &mut context, weight, &budget, 1).unwrap(); - let utility = fee_utility(weight, &fee, &budget); + let fee = indexer_fee(&cost_model, &mut context, &budget, 1).unwrap(); + let utility = fee_utility(&fee, &budget); let utility = utility.utility.powf(utility.weight); assert!(fee >= "0.01".parse::().unwrap()); assert_within(utility, expected_utility, 0.0001); diff --git a/indexer-selection/src/indexing.rs b/indexer-selection/src/indexing.rs index 166667b2..548852f0 100644 --- a/indexer-selection/src/indexing.rs +++ b/indexer-selection/src/indexing.rs @@ -146,16 +146,9 @@ impl IndexingState { pub fn fee( &self, context: &mut Context<'_>, - weight: f64, budget: &GRT, max_indexers: u8, ) -> Result { - indexer_fee( - &self.status.cost_model, - context, - weight, - budget, - max_indexers, - ) + indexer_fee(&self.status.cost_model, context, budget, max_indexers) } } diff --git a/indexer-selection/src/lib.rs b/indexer-selection/src/lib.rs index 56cfc87d..bbb01a97 100644 --- a/indexer-selection/src/lib.rs +++ b/indexer-selection/src/lib.rs @@ -168,53 +168,7 @@ pub struct UtilityParameters { pub budget: GRT, pub requirements: BlockRequirements, pub latest_block: u64, - pub performance: ConcaveUtilityParameters, - pub data_freshness: ConcaveUtilityParameters, - pub economic_security: ConcaveUtilityParameters, - pub fee_weight: f64, -} - -impl UtilityParameters { - #[allow(clippy::too_many_arguments)] - pub fn new( - budget: GRT, - requirements: BlockRequirements, - latest_block: u64, - block_rate_hz: f64, - performance: f64, - data_freshness: f64, - economic_security: f64, - fee_weight: f64, - ) -> Self { - fn interp(lo: f64, hi: f64, preference: f64) -> f64 { - if !(0.0..=1.0).contains(&preference) { - return lo; - } - lo + ((hi - lo) * preference) - } - Self { - budget, - requirements, - latest_block, - // 170cbcf3-db7f-404a-be13-2022d9142677 - performance: ConcaveUtilityParameters { - a: interp(1.1, 1.2, performance), - weight: interp(1.0, 1.5, performance), - }, - // 9f6c6cb0-0e49-4bc4-848e-22a1599af45b - data_freshness: ConcaveUtilityParameters { - a: 32.0 * block_rate_hz, - weight: interp(1.0, 2.0, data_freshness), - }, - // https://www.desmos.com/calculator/g7t53e70lf - economic_security: ConcaveUtilityParameters { - a: interp(8e-4, 4e-4, economic_security), - weight: interp(1.0, 1.5, economic_security), - }, - // 3534cc5a-f562-48ce-ac7a-88737c80698b - fee_weight: interp(1.0, 2.0, fee_weight), - } - } + pub block_rate_hz: f64, } #[derive(Default)] @@ -350,7 +304,6 @@ impl State { let fee = indexer_fee( &state.status.cost_model, context, - params.fee_weight, ¶ms.budget, selection_limit, )?; diff --git a/indexer-selection/src/performance.rs b/indexer-selection/src/performance.rs index 43829fec..86f9227d 100644 --- a/indexer-selection/src/performance.rs +++ b/indexer-selection/src/performance.rs @@ -6,16 +6,14 @@ use crate::{ impl_struct_decay, score::ExpectedValue, utility::UtilityFactor, - ConcaveUtilityParameters, }; -// https://www.desmos.com/calculator/y2t5704v6a -// 170cbcf3-db7f-404a-be13-2022d9142677 -pub fn performance_utility(params: ConcaveUtilityParameters, latency_ms: u32) -> UtilityFactor { - let sigmoid = |x: u32| 1.0 + E.powf(((x as f64).powf(params.a) - 400.0) / 300.0); +// https://www.desmos.com/calculator/rvqjvypylj +pub fn performance_utility(latency_ms: u32) -> UtilityFactor { + let sigmoid = |x: u32| 1.0 + E.powf(((x as f64).powf(1.1) - 400.0) / 300.0); UtilityFactor { utility: sigmoid(0) / sigmoid(latency_ms), - weight: params.weight, + weight: 1.0, } } diff --git a/indexer-selection/src/score.rs b/indexer-selection/src/score.rs index faffef70..289adcdd 100644 --- a/indexer-selection/src/score.rs +++ b/indexer-selection/src/score.rs @@ -188,14 +188,12 @@ impl MetaIndexer<'_> { let factors = [ reliability_utility(p_success).mul_weight(exploration), - performance_utility(params.performance, perf_success as u32) - .mul_weight(exploration * p_success), - performance_utility(params.performance, perf_failure as u32) - .mul_weight(exploration * (1.0 - p_success)), - params.economic_security.concave_utility(slashable_usd), + performance_utility(perf_success as u32).mul_weight(exploration * p_success), + performance_utility(perf_failure as u32).mul_weight(exploration * (1.0 - p_success)), + economic_security_utility(slashable_usd), versions_behind_utility(versions_behind), - data_freshness_utility(params.data_freshness, ¶ms.requirements, blocks_behind), - fee_utility(params.fee_weight, &self.fee(), ¶ms.budget), + data_freshness_utility(params.block_rate_hz, ¶ms.requirements, blocks_behind), + fee_utility(&self.fee(), ¶ms.budget), ]; let score = weighted_product_model(factors); @@ -220,35 +218,48 @@ pub fn expected_individual_score( ) -> f64 { weighted_product_model([ reliability_utility(reliability), - performance_utility(params.performance, perf_success as u32), - params.economic_security.concave_utility(slashable_usd), + performance_utility(perf_success as u32), + economic_security_utility(slashable_usd), versions_behind_utility(versions_behind), - data_freshness_utility(params.data_freshness, ¶ms.requirements, blocks_behind), - fee_utility(params.fee_weight, fee, ¶ms.budget), + data_freshness_utility(params.block_rate_hz, ¶ms.requirements, blocks_behind), + fee_utility(fee, ¶ms.budget), ]) } +// https://www.desmos.com/calculator/dxgonxuihk +fn economic_security_utility(slashable_usd: f64) -> UtilityFactor { + ConcaveUtilityParameters { + a: 4e-4, + weight: 1.5, + } + .concave_utility(slashable_usd) +} + /// https://www.desmos.com/calculator/plpijnbvhu fn reliability_utility(p_success: f64) -> UtilityFactor { UtilityFactor::one(p_success.powi(7)) } -/// https://www.desmos.com/calculator/6unqha22hp -/// 9f6c6cb0-0e49-4bc4-848e-22a1599af45b +/// https://www.desmos.com/calculator/mioowuofsj fn data_freshness_utility( - params: ConcaveUtilityParameters, + block_rate_hz: f64, requirements: &BlockRequirements, blocks_behind: u64, ) -> UtilityFactor { + let weight = 2.0; // Add utility if the latest block is requested. Otherwise, data freshness is not a utility, // but a binary of minimum block. Note that it can be both. if !requirements.has_latest || (blocks_behind == 0) { UtilityFactor { utility: 1.0, - weight: params.weight, + weight, } } else { - params.concave_utility(1.0 / blocks_behind as f64) + ConcaveUtilityParameters { + a: 32.0 * block_rate_hz, + weight, + } + .concave_utility(1.0 / blocks_behind as f64) } } diff --git a/indexer-selection/src/test.rs b/indexer-selection/src/test.rs index f2ebc297..b0a35f6e 100644 --- a/indexer-selection/src/test.rs +++ b/indexer-selection/src/test.rs @@ -74,7 +74,12 @@ fn utiliy_params( requirements: BlockRequirements, latest_block: u64, ) -> UtilityParameters { - UtilityParameters::new(budget, requirements, latest_block, 0.1, 0.0, 0.0, 0.0, 0.0) + UtilityParameters { + budget, + requirements, + latest_block, + block_rate_hz: 0.1, + } } impl Topology {