Skip to content

Commit

Permalink
fix: remove application of user preferences in indexer-selection
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Theodus committed Sep 14, 2023
1 parent 5f418ae commit a1fb747
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 150 deletions.
12 changes: 2 additions & 10 deletions graph-gateway/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,7 +30,6 @@ pub struct AuthHandler {
#[derive(Debug)]
pub struct UserSettings {
pub budget: Option<USD>,
pub indexer_preferences: IndexerPreferences,
}

pub enum AuthToken {
Expand Down Expand Up @@ -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 }
}
}

Expand Down
15 changes: 6 additions & 9 deletions graph-gateway/src/client_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
33 changes: 0 additions & 33 deletions graph-gateway/src/subgraph_studio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub struct APIKey {
pub deployments: Vec<DeploymentId>,
pub subgraphs: Vec<SubgraphId>,
pub domains: Vec<String>,
pub indexer_preferences: IndexerPreferences,
}

#[derive(Clone, Copy, Debug, Default, Deserialize)]
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -126,7 +102,6 @@ impl Client {
.into_iter()
.map(|domain| domain.domain)
.collect(),
indexer_preferences,
};
Some((api_key.key.clone(), Arc::new(api_key)))
})
Expand All @@ -152,21 +127,13 @@ struct GatewayApiKey {
query_status: QueryStatus,
max_budget: Option<f64>,
#[serde(default)]
indexer_preferences: Vec<GatewayIndexerPreference>,
#[serde(default)]
subgraphs: Vec<GatewaySubgraph>,
#[serde(default)]
deployments: Vec<String>,
#[serde(default)]
domains: Vec<GatewayDomain>,
}

#[derive(Deserialize)]
struct GatewayIndexerPreference {
name: String,
weight: f64,
}

#[derive(Deserialize)]
struct GatewaySubgraph {
network_subgraph_id: String,
Expand Down
12 changes: 4 additions & 8 deletions indexer-selection/bin/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,12 @@ async fn main() -> Result<()> {
gen_blocks(&(0..last_block).collect::<Vec<u64>>())
};
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) |");
Expand Down
26 changes: 15 additions & 11 deletions indexer-selection/src/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -105,7 +111,6 @@ fn min_optimal_fee(weight: f64, budget: &GRT) -> GRT {
pub fn indexer_fee(
cost_model: &Option<Ptr<CostModel>>,
context: &mut Context<'_>,
weight: f64,
budget: &GRT,
max_indexers: u8,
) -> Result<GRT, SelectionError> {
Expand Down Expand Up @@ -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 {
Expand All @@ -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::<GRT>().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::<GRT>().unwrap());
assert_within(utility, expected_utility, 0.0001);
Expand Down
9 changes: 1 addition & 8 deletions indexer-selection/src/indexing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,9 @@ impl IndexingState {
pub fn fee(
&self,
context: &mut Context<'_>,
weight: f64,
budget: &GRT,
max_indexers: u8,
) -> Result<GRT, SelectionError> {
indexer_fee(
&self.status.cost_model,
context,
weight,
budget,
max_indexers,
)
indexer_fee(&self.status.cost_model, context, budget, max_indexers)
}
}
49 changes: 1 addition & 48 deletions indexer-selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -350,7 +304,6 @@ impl State {
let fee = indexer_fee(
&state.status.cost_model,
context,
params.fee_weight,
&params.budget,
selection_limit,
)?;
Expand Down
10 changes: 4 additions & 6 deletions indexer-selection/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
Loading

0 comments on commit a1fb747

Please sign in to comment.