From c119128c2bbe718e8073e11dca6dd263ac85b5fd Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 30 May 2024 17:04:50 -0700 Subject: [PATCH 01/40] include coverage point calculator in mobile-verifier --- Cargo.lock | 1 + mobile_verifier/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6c27516d8..402796031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4749,6 +4749,7 @@ dependencies = [ "chrono", "clap 4.4.8", "config", + "coverage-point-calculator", "custom-tracing", "db-store", "derive_builder", diff --git a/mobile_verifier/Cargo.toml b/mobile_verifier/Cargo.toml index 75d86b1ec..217b1a80a 100644 --- a/mobile_verifier/Cargo.toml +++ b/mobile_verifier/Cargo.toml @@ -58,6 +58,7 @@ regex = "1" humantime-serde = { workspace = true } custom-tracing = { path = "../custom_tracing" } hex-assignments = { path = "../hex_assignments" } +coverage-point-calculator = { path = "../coverage_point_calculator" } [dev-dependencies] backon = "0" From 2d916a554e65ea34bfbb4fca4ce50b7596c2e3dd Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 30 May 2024 17:06:30 -0700 Subject: [PATCH 02/40] derive Debug for nested types will help with the switchover --- mobile_verifier/src/coverage.rs | 2 +- mobile_verifier/src/speedtests_average.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile_verifier/src/coverage.rs b/mobile_verifier/src/coverage.rs index 25f10de97..ba0813a18 100644 --- a/mobile_verifier/src/coverage.rs +++ b/mobile_verifier/src/coverage.rs @@ -358,7 +358,7 @@ impl CoverageObject { } } -#[derive(Clone, FromRow)] +#[derive(Debug, Clone, FromRow)] pub struct HexCoverage { pub uuid: Uuid, #[sqlx(try_from = "i64")] diff --git a/mobile_verifier/src/speedtests_average.rs b/mobile_verifier/src/speedtests_average.rs index 115bf0537..04337673d 100644 --- a/mobile_verifier/src/speedtests_average.rs +++ b/mobile_verifier/src/speedtests_average.rs @@ -199,7 +199,7 @@ impl SpeedtestTier { } } -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] pub struct SpeedtestAverages { pub averages: HashMap, } From 17eee396e9f6e3af3971a7b0fbb906f188108762 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 30 May 2024 17:08:11 -0700 Subject: [PATCH 03/40] CoveragePoints uses CoveragePoint2 internally This makes coverage points async internally. When cleanup is done there will be a single instance of CoveragePoints. --- mobile_verifier/src/cli/reward_from_db.rs | 1 + mobile_verifier/src/reward_shares.rs | 1151 +++++++++++++++------ mobile_verifier/src/rewarder.rs | 5 +- 3 files changed, 846 insertions(+), 311 deletions(-) diff --git a/mobile_verifier/src/cli/reward_from_db.rs b/mobile_verifier/src/cli/reward_from_db.rs index ce0a08f21..d51c48ee1 100644 --- a/mobile_verifier/src/cli/reward_from_db.rs +++ b/mobile_verifier/src/cli/reward_from_db.rs @@ -55,6 +55,7 @@ impl Cmd { let mut owner_rewards = HashMap::<_, u64>::new(); let radio_rewards = reward_shares .into_rewards(Decimal::ZERO, &epoch) + .await .ok_or(anyhow::anyhow!("no rewardable events"))?; for (_reward_amount, reward) in radio_rewards { if let Some(proto::mobile_reward_share::Reward::RadioReward(proto::RadioReward { diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 632df340e..f086f6379 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1,9 +1,9 @@ use crate::{ - coverage::{CoverageReward, CoverageRewardPoints, CoveredHexStream, CoveredHexes}, + coverage::{CoveredHexStream, HexCoverage, Seniority}, data_session::{HotspotMap, ServiceProviderDataSession}, heartbeats::{HeartbeatReward, OwnedKeyType}, radio_threshold::VerifiedRadioThresholds, - speedtests_average::{SpeedtestAverage, SpeedtestAverages}, + speedtests_average::SpeedtestAverages, subscriber_location::SubscriberValidatedLocations, }; use chrono::{DateTime, Duration, Utc}; @@ -20,12 +20,12 @@ use helium_proto::{ ServiceProvider, }; use mobile_config::{ - boosted_hex_info::{BoostedHex, BoostedHexes}, + boosted_hex_info::BoostedHexes, client::{carrier_service_client::CarrierServiceVerifier, ClientError}, }; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -use std::{collections::HashMap, num::NonZeroU32, ops::Range}; +use std::{collections::HashMap, ops::Range}; use uuid::Uuid; /// Total tokens emissions pool per 365 days or 366 days for a leap year @@ -375,118 +375,322 @@ pub fn dc_to_mobile_bones(dc_amount: Decimal, mobile_bone_price: Decimal) -> Dec .round_dp_with_strategy(DEFAULT_PREC, RoundingStrategy::ToPositiveInfinity) } -#[derive(Debug)] -struct CoverageRewardPointsWithMultiplier { - coverage_points: CoverageRewardPoints, - boosted_hex: BoostedHex, -} +type RadioId = (PublicKeyBinary, Option); #[derive(Debug)] -struct RadioPoints { - location_trust_score_multiplier: Decimal, - coverage_object: Uuid, - seniority: DateTime, - // Boosted hexes are included in CoverageRewardPointsWithMultiplier, - // this gets included in the radio reward share proto - reward_points: Vec, +pub struct CoveragePoints2 { + boosted_hexes: BoostedHexes, + verified_radio_thresholds: VerifiedRadioThresholds, + speedtest_averages: SpeedtestAverages, + radio_heartbeats: HashMap>, + coverage_map: HashMap>, + reward_period: Range>, + seniority_timestamps: HashMap>, } -impl RadioPoints { - fn new( - location_trust_score_multiplier: Decimal, - coverage_object: Uuid, - seniority: DateTime, - ) -> Self { - Self { - location_trust_score_multiplier, - seniority, - coverage_object, - reward_points: vec![], +impl CoveragePoints2 { + pub async fn new( + hex_streams: &impl CoveredHexStream, + heartbeats: impl Stream>, + speedtest_averages: SpeedtestAverages, + boosted_hexes: BoostedHexes, + verified_radio_thresholds: VerifiedRadioThresholds, + reward_period: &Range>, + ) -> anyhow::Result { + let mut radio_heartbeats: HashMap> = HashMap::new(); + let mut coverage_map: HashMap> = HashMap::new(); + let mut seniority_timestamps: HashMap> = HashMap::new(); + + let mut heartbeats = std::pin::pin!(heartbeats); + while let Some(heartbeat) = heartbeats.next().await.transpose()? { + let pubkey = heartbeat.hotspot_key.clone(); + let heartbeat_key = heartbeat.key().clone(); + let cbsd_id = heartbeat_key.to_owned().into_cbsd_id(); + let key = (pubkey, cbsd_id); + + // * --- + let period_end = reward_period.end; + let seniority = hex_streams + .fetch_seniority(heartbeat_key, period_end) + .await?; + { + let covered_hexes = hex_streams + .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) + .await?; + + let mut covered_hexes = std::pin::pin!(covered_hexes); + while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { + coverage_map + .entry(key.clone()) + .or_default() + .push(hex_coverage); + } + } + + seniority_timestamps + .entry(key.clone()) + .or_default() + .push(seniority); + + // * --- + radio_heartbeats + .entry(key) + .or_default() + .push(heartbeat.clone()); } + + Ok(Self { + boosted_hexes, + verified_radio_thresholds, + speedtest_averages, + radio_heartbeats, + coverage_map, + seniority_timestamps, + reward_period: reward_period.clone(), + }) } - fn hex_points(&self) -> Decimal { - self.reward_points - .iter() - .map(|c| c.coverage_points.points() * Decimal::from(c.boosted_hex.multiplier.get())) - .sum::() + pub async fn all_radio_ids( + &mut self, + ) -> anyhow::Result)>> { + Ok(self.radio_heartbeats.keys().cloned().collect()) } - fn coverage_points(&self) -> Decimal { - let coverage_points = self.hex_points(); - (self.location_trust_score_multiplier * coverage_points).max(Decimal::ZERO) + pub fn get_seniority(&self, key: &RadioId) -> Option { + let mut s = self + .seniority_timestamps + .get(key) + .expect("seniority timestamps") + .clone(); + s.sort_by_key(|f| f.seniority_ts); + s.first().cloned() } - fn poc_reward(&self, reward_per_share: Decimal, speedtest_multiplier: Decimal) -> Decimal { - reward_per_share * speedtest_multiplier * self.coverage_points() + fn radio_type(&self, key: &RadioId) -> coverage_point_calculator::RadioType { + let hex_coverage = self + .coverage_map + .get(key) + .expect("coverage map entry for radio_type") + .first() + .expect("at least one hex coverage for radio type"); + + match (hex_coverage.indoor, &hex_coverage.radio_key) { + (true, OwnedKeyType::Cbrs(_)) => coverage_point_calculator::RadioType::IndoorCbrs, + (true, OwnedKeyType::Wifi(_)) => coverage_point_calculator::RadioType::IndoorWifi, + (false, OwnedKeyType::Cbrs(_)) => coverage_point_calculator::RadioType::OutdoorCbrs, + (false, OwnedKeyType::Wifi(_)) => coverage_point_calculator::RadioType::OutdoorWifi, + } } -} -// pub type HotspotBoostedHexes = HashMap; + fn radio_threshold_verified( + &self, + key: &RadioId, + ) -> coverage_point_calculator::SubscriberThreshold { + match self + .verified_radio_thresholds + .is_verified(key.0.clone(), key.1.clone()) + { + true => coverage_point_calculator::SubscriberThreshold::Verified, + false => coverage_point_calculator::SubscriberThreshold::UnVerified, + } + } -#[derive(Debug, Default)] -struct HotspotPoints { - /// Points are multiplied by the multiplier to get shares. - /// Multiplier should never be zero. - speedtest_multiplier: Decimal, - radio_points: HashMap, RadioPoints>, -} + pub fn get_coverage_object_uuid(&self, key: &RadioId) -> Option { + self.radio_heartbeats + .get(key) + .and_then(|v| v.first()) + .map(|v| v.coverage_object) + } -impl HotspotPoints { - pub fn add_coverage_entry( + pub async fn coverage_points( &mut self, - radio_key: OwnedKeyType, - hotspot: PublicKeyBinary, - points: CoverageRewardPoints, - boosted_hex_info: BoostedHex, - verified_radio_thresholds: &VerifiedRadioThresholds, - ) { - let cbsd_id = radio_key.clone().into_cbsd_id(); - let rp = self.radio_points.get_mut(&cbsd_id).unwrap(); - // need to consider requirements from hip93 & hip84 before applying any boost - // hip93: if radio is wifi & location_trust score multiplier < 0.75, no boosting - // hip84: if radio has not met minimum data and subscriber thresholds, no boosting - let final_boost_info = if radio_key.is_wifi() - && rp.location_trust_score_multiplier < dec!(0.75) - || !verified_radio_thresholds.is_verified(hotspot, cbsd_id) - { - BoostedHex { - location: boosted_hex_info.location, - multiplier: NonZeroU32::new(1).unwrap(), + radio_id: &RadioId, + ) -> anyhow::Result { + struct Temp { + radio_id: RadioId, + radio_type: coverage_point_calculator::RadioType, + speedtests: Vec, + trust_scores: Vec, + verified: coverage_point_calculator::SubscriberThreshold, + } + + impl coverage_point_calculator::Radio for Temp { + fn key(&self) -> RadioId { + self.radio_id.clone() } - } else { - boosted_hex_info - }; - rp.reward_points.push(CoverageRewardPointsWithMultiplier { - coverage_points: points, - boosted_hex: final_boost_info, - }); + fn radio_type(&self) -> coverage_point_calculator::RadioType { + self.radio_type.clone() + } + + fn speedtests(&self) -> Vec { + self.speedtests.clone() + } + + fn location_trust_scores( + &self, + ) -> Vec { + self.trust_scores.clone() + } + + fn verified_radio_threshold(&self) -> coverage_point_calculator::SubscriberThreshold { + self.verified.clone() + } + } + + let (pubkey, _cbsd_id) = &radio_id; + + // * speedtest + let mut speedtests = vec![]; + let average = self.speedtest_averages.get_average(pubkey).unwrap(); + for test in average.speedtests { + use coverage_point_calculator::speedtest; + speedtests.push(speedtest::Speedtest { + upload_speed: speedtest::BytesPs::new(test.report.upload_speed), + download_speed: speedtest::BytesPs::new(test.report.download_speed), + latency: speedtest::Millis::new(test.report.latency), + }); + } + + // * trust scores + let mut trust_scores = vec![]; + for heartbeat in self.radio_heartbeats.get(&radio_id).unwrap() { + use coverage_point_calculator::location; + + // FIXME: what do we do for radios with no asserted distances? + // ? unwrapping an option of distances? + let fallback: Vec = std::iter::repeat(0) + .take(heartbeat.trust_score_multipliers.len()) + .collect(); + let combos = heartbeat + .distances_to_asserted + .as_ref() + .unwrap_or(&fallback) + .iter() + .zip(heartbeat.trust_score_multipliers.iter()); + + for (&distance, &trust_score) in combos { + trust_scores.push(location::LocationTrust { + distance_to_asserted: location::Meters::new(distance as u32), + trust_score, + }) + } + } + + let temp = Temp { + radio_id: radio_id.clone(), + radio_type: self.radio_type(&radio_id), + speedtests, + trust_scores, + verified: self.radio_threshold_verified(&radio_id), + }; + let radio = coverage_point_calculator::make_rewardable_radio(&temp, self); + Ok(coverage_point_calculator::calculate_coverage_points(radio)) } } -impl HotspotPoints { - pub fn new(speedtest_multiplier: Decimal) -> Self { - Self { - speedtest_multiplier, - radio_points: HashMap::new(), - } +pub fn coverage_point_to_mobile_reward_share( + coverage_points: coverage_point_calculator::CoveragePoints, + start_period: u64, + end_period: u64, + hotspot_key: &PublicKeyBinary, + cbsd_id: Option, + poc_reward: u64, + seniority_timestamp: DateTime, + coverage_object_uuid: Uuid, +) -> proto::MobileRewardShare { + println!("\nBoosted Hexes:"); + println!(" radio: {hotspot_key}"); + let boosted_hexes = coverage_points + .radio + .boosted_hexes() + .map(|covered_hex| proto::BoostedHex { + location: covered_hex.cell.into_raw(), + multiplier: covered_hex.boosted.unwrap().into(), + }) + .collect::>(); + println!(" boosted count: {}", boosted_hexes.len()); + + proto::MobileRewardShare { + start_period, + end_period, + reward: Some(proto::mobile_reward_share::Reward::RadioReward( + proto::RadioReward { + hotspot_key: hotspot_key.clone().into(), + cbsd_id: cbsd_id.unwrap_or_default(), + poc_reward, + coverage_points: coverage_points + .radio + .hex_coverage_points() + .to_u64() + .unwrap_or(0), + seniority_timestamp: seniority_timestamp.encode_timestamp(), + coverage_object: Vec::from(coverage_object_uuid.into_bytes()), + location_trust_score_multiplier: coverage_points + .reward_share_location_trust_multiplier(), + speedtest_multiplier: coverage_points.reward_share_speedtest_multiplier(), + boosted_hexes, + ..Default::default() + }, + )), } } -impl HotspotPoints { - pub fn total_points(&self) -> Decimal { - self.speedtest_multiplier - * self - .radio_points - .values() - .fold(Decimal::ZERO, |sum, radio| sum + radio.coverage_points()) +impl coverage_point_calculator::CoverageMap for CoveragePoints2 { + fn hexes( + &self, + radio: &impl coverage_point_calculator::Radio, + ) -> Vec { + use crate::coverage; + use coverage_point_calculator as cpc; + + let key = radio.key(); + self.coverage_map + .get(&key) + .expect("coverage map") + .into_iter() + .map(|hex| { + let boosted_hex = self + .boosted_hexes + .get_current_multiplier(hex.hex, self.reward_period.start); + coverage_point_calculator::CoveredHex { + cell: hex.hex, + // TODO: fix the rank + rank: coverage_point_calculator::Rank::new(1).unwrap(), + signal_level: match hex.signal_level { + coverage::SignalLevel::None => cpc::SignalLevel::None, + coverage::SignalLevel::Low => cpc::SignalLevel::Low, + coverage::SignalLevel::Medium => cpc::SignalLevel::Medium, + coverage::SignalLevel::High => cpc::SignalLevel::High, + }, + assignments: coverage_point_calculator::Assignments { + footfall: match hex.assignments.footfall { + hex_assignments::Assignment::A => cpc::Assignment::A, + hex_assignments::Assignment::B => cpc::Assignment::B, + hex_assignments::Assignment::C => cpc::Assignment::C, + }, + landtype: match hex.assignments.landtype { + hex_assignments::Assignment::A => cpc::Assignment::A, + hex_assignments::Assignment::B => cpc::Assignment::B, + hex_assignments::Assignment::C => cpc::Assignment::C, + }, + urbanized: match hex.assignments.urbanized { + hex_assignments::Assignment::A => cpc::Assignment::A, + hex_assignments::Assignment::B => cpc::Assignment::B, + hex_assignments::Assignment::C => cpc::Assignment::C, + }, + }, + boosted: boosted_hex, + } + }) + .collect::>() } } #[derive(Debug)] pub struct CoveragePoints { - coverage_points: HashMap, + // coverage_points: HashMap, + coverage_points: CoveragePoints2, } impl CoveragePoints { @@ -497,181 +701,253 @@ impl CoveragePoints { boosted_hexes: &BoostedHexes, verified_radio_thresholds: &VerifiedRadioThresholds, reward_period: &Range>, - ) -> Result { - let mut heartbeats = std::pin::pin!(heartbeats); - let mut covered_hexes = CoveredHexes::default(); - let mut coverage_points = HashMap::new(); - while let Some(heartbeat) = heartbeats.next().await.transpose()? { - let speedtest_multiplier = speedtests - .get_average(&heartbeat.hotspot_key) - .as_ref() - .map_or(Decimal::ZERO, SpeedtestAverage::reward_multiplier); - let seniority = hex_streams - .fetch_seniority(heartbeat.key(), reward_period.end) - .await?; - let covered_hex_stream = hex_streams - .covered_hex_stream(heartbeat.key(), &heartbeat.coverage_object, &seniority) - .await?; - let overlaps_boosted = covered_hexes - .aggregate_coverage(&heartbeat.hotspot_key, boosted_hexes, covered_hex_stream) - .await?; - let opt_cbsd_id = heartbeat.key().to_owned().into_cbsd_id(); - coverage_points - .entry(heartbeat.hotspot_key.clone()) - .or_insert_with(|| HotspotPoints::new(speedtest_multiplier)) - .radio_points - .insert( - opt_cbsd_id, - RadioPoints::new( - heartbeat.trust_score_multiplier(overlaps_boosted), - heartbeat.coverage_object, - seniority.seniority_ts, - ), - ); - } - - for CoverageReward { - radio_key, - points, - hotspot, - boosted_hex_info, - } in covered_hexes.into_coverage_rewards(boosted_hexes, reward_period.start) - { - // Guaranteed that points contains the given hotspot. - coverage_points - .get_mut(&hotspot) - .unwrap() - .add_coverage_entry( - radio_key, - hotspot, - points, - boosted_hex_info, - verified_radio_thresholds, - ) - } + ) -> anyhow::Result { + let coverage_points = CoveragePoints2::new( + hex_streams, + heartbeats, + speedtests.clone(), + boosted_hexes.clone(), + verified_radio_thresholds.clone(), + reward_period, + ) + .await?; Ok(Self { coverage_points }) + // let mut heartbeats = std::pin::pin!(heartbeats); + // let mut covered_hexes = CoveredHexes::default(); + // let mut coverage_points = HashMap::new(); + // while let Some(heartbeat) = heartbeats.next().await.transpose()? { + // let speedtest_multiplier = speedtests + // .get_average(&heartbeat.hotspot_key) + // .as_ref() + // .map_or(Decimal::ZERO, SpeedtestAverage::reward_multiplier); + // let seniority = hex_streams + // .fetch_seniority(heartbeat.key(), reward_period.end) + // .await?; + // let covered_hex_stream = hex_streams + // .covered_hex_stream(heartbeat.key(), &heartbeat.coverage_object, &seniority) + // .await?; + // let overlaps_boosted = covered_hexes + // .aggregate_coverage(&heartbeat.hotspot_key, boosted_hexes, covered_hex_stream) + // .await?; + // let opt_cbsd_id = heartbeat.key().to_owned().into_cbsd_id(); + // coverage_points + // .entry(heartbeat.hotspot_key.clone()) + // .or_insert_with(|| HotspotPoints::new(speedtest_multiplier)) + // .radio_points + // .insert( + // opt_cbsd_id, + // RadioPoints::new( + // heartbeat.trust_score_multiplier(overlaps_boosted), + // heartbeat.coverage_object, + // seniority.seniority_ts, + // ), + // ); + // } + + // for CoverageReward { + // radio_key, + // points, + // hotspot, + // boosted_hex_info, + // } in covered_hexes.into_coverage_rewards(boosted_hexes, reward_period.start) + // { + // // Guaranteed that points contains the given hotspot. + // coverage_points + // .get_mut(&hotspot) + // .unwrap() + // .add_coverage_entry( + // radio_key, + // hotspot, + // points, + // boosted_hex_info, + // verified_radio_thresholds, + // ) + // } + // Ok(Self { coverage_points }) } /// Only used for testing - pub fn hotspot_points(&self, hotspot: &PublicKeyBinary) -> Decimal { - self.coverage_points - .get(hotspot) - .map(HotspotPoints::total_points) - .unwrap_or(Decimal::ZERO) + pub fn hotspot_points(&self, _hotspot: &PublicKeyBinary) -> Decimal { + todo!() + // self.coverage_points + // .get(hotspot) + // .map(HotspotPoints::total_points) + // .unwrap_or(Decimal::ZERO) } pub fn total_shares(&self) -> Decimal { - self.coverage_points - .values() - .fold(Decimal::ZERO, |sum, radio_points| { - sum + radio_points.total_points() - }) + todo!() + // self.coverage_points + // .values() + // .fold(Decimal::ZERO, |sum, radio_points| { + // sum + radio_points.total_points() + // }) } - pub fn into_rewards( - self, + pub async fn into_rewards( + mut self, available_poc_rewards: Decimal, epoch: &'_ Range>, ) -> Option + '_> { - let total_shares = self.total_shares(); - available_poc_rewards - .checked_div(total_shares) - .map(|poc_rewards_per_share| { - tracing::info!(%poc_rewards_per_share); - let start_period = epoch.start.encode_timestamp(); - let end_period = epoch.end.encode_timestamp(); - self.coverage_points - .into_iter() - .flat_map(move |(hotspot_key, hotspot_points)| { - radio_points_into_rewards( - hotspot_key, - start_period, - end_period, - poc_rewards_per_share, - hotspot_points.speedtest_multiplier, - hotspot_points.radio_points.into_iter(), - ) - }) - .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0) - }) - } -} + println!("-- available poc rewards: {available_poc_rewards}"); + let radio_ids = self + .coverage_points + .all_radio_ids() + .await + .expect("radio ids"); + + let mut total_shares: Decimal = dec!(0); + + let mut stuff = vec![]; + for id in radio_ids { + let points = self + .coverage_points + .coverage_points(&id) + .await + .expect("coverage points"); + let seniority = self + .coverage_points + .get_seniority(&id) + .expect("seniority timestamp"); + let coverage_object_uuid = self + .coverage_points + .get_coverage_object_uuid(&id) + .expect("coverage_object_uuid"); + + total_shares += points.coverage_points; + stuff.push((id, points, seniority, coverage_object_uuid)); + } -fn radio_points_into_rewards( - hotspot_key: PublicKeyBinary, - start_period: u64, - end_period: u64, - poc_rewards_per_share: Decimal, - speedtest_multiplier: Decimal, - radio_points: impl Iterator, RadioPoints)>, -) -> impl Iterator { - radio_points.map(move |(cbsd_id, radio_points)| { - new_radio_reward( - cbsd_id, - &hotspot_key, - start_period, - end_period, - poc_rewards_per_share, - speedtest_multiplier, - radio_points, + let Some(rewards_per_share) = available_poc_rewards.checked_div(total_shares) else { + // there are no shares, the rest are unallocated, return early + return None; + }; + + Some( + stuff + .into_iter() + .map(move |(id, points, seniority, coverage_object_uuid)| { + let poc_reward = rewards_per_share * points.coverage_points; + let poc_reward = poc_reward + .round_dp_with_strategy(0, RoundingStrategy::ToZero) + .to_u64() + .unwrap_or_default(); + + let mobile_reward_share = coverage_point_to_mobile_reward_share( + points, + epoch.start.encode_timestamp(), + epoch.end.encode_timestamp(), + &id.0, + id.1, + poc_reward, + seniority.seniority_ts, + coverage_object_uuid, + ); + (poc_reward, mobile_reward_share) + }) + .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0), ) - }) -} -#[allow(clippy::too_many_arguments)] -fn new_radio_reward( - cbsd_id: Option, - hotspot_key: &PublicKeyBinary, - start_period: u64, - end_period: u64, - poc_rewards_per_share: Decimal, - speedtest_multiplier: Decimal, - radio_points: RadioPoints, -) -> (u64, proto::MobileRewardShare) { - let poc_reward = radio_points.poc_reward(poc_rewards_per_share, speedtest_multiplier); - let hotspot_key: Vec = hotspot_key.clone().into(); - let cbsd_id = cbsd_id.unwrap_or_default(); - let poc_reward = poc_reward - .round_dp_with_strategy(0, RoundingStrategy::ToZero) - .to_u64() - .unwrap_or(0); - let boosted_hexes = radio_points - .reward_points - .iter() - .filter(|radio_points| radio_points.boosted_hex.multiplier > NonZeroU32::new(1).unwrap()) - .map(|radio_points| proto::BoostedHex { - location: radio_points.boosted_hex.location.into_raw(), - multiplier: radio_points.boosted_hex.multiplier.get(), - }) - .collect(); - ( - poc_reward, - proto::MobileRewardShare { - start_period, - end_period, - reward: Some(proto::mobile_reward_share::Reward::RadioReward( - proto::RadioReward { - hotspot_key, - cbsd_id, - poc_reward, - coverage_points: radio_points.coverage_points().to_u64().unwrap_or(0), - seniority_timestamp: radio_points.seniority.encode_timestamp(), - coverage_object: Vec::from(radio_points.coverage_object.into_bytes()), - location_trust_score_multiplier: (radio_points.location_trust_score_multiplier - * dec!(1000)) - .to_u32() - .unwrap_or_default(), - speedtest_multiplier: (speedtest_multiplier * dec!(1000)) - .to_u32() - .unwrap_or_default(), - boosted_hexes, - ..Default::default() - }, - )), - }, - ) + // let total_shares = self.total_shares(); + // available_poc_rewards + // .checked_div(total_shares) + // .map(|poc_rewards_per_share| { + // tracing::info!(%poc_rewards_per_share); + // let start_period = epoch.start.encode_timestamp(); + // let end_period = epoch.end.encode_timestamp(); + // self.coverage_points + // .into_iter() + // .flat_map(move |(hotspot_key, hotspot_points)| { + // radio_points_into_rewards( + // hotspot_key, + // start_period, + // end_period, + // poc_rewards_per_share, + // hotspot_points.speedtest_multiplier, + // hotspot_points.radio_points.into_iter(), + // ) + // }) + // .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0) + // }) + } } +// fn radio_points_into_rewards( +// hotspot_key: PublicKeyBinary, +// start_period: u64, +// end_period: u64, +// poc_rewards_per_share: Decimal, +// speedtest_multiplier: Decimal, +// radio_points: impl Iterator, RadioPoints)>, +// ) -> impl Iterator { +// radio_points.map(move |(cbsd_id, radio_points)| { +// new_radio_reward( +// cbsd_id, +// &hotspot_key, +// start_period, +// end_period, +// poc_rewards_per_share, +// speedtest_multiplier, +// radio_points, +// ) +// }) +// } + +// #[allow(clippy::too_many_arguments)] +// fn new_radio_reward( +// cbsd_id: Option, +// hotspot_key: &PublicKeyBinary, +// start_period: u64, +// end_period: u64, +// poc_rewards_per_share: Decimal, +// speedtest_multiplier: Decimal, +// radio_points: RadioPoints, +// ) -> (u64, proto::MobileRewardShare) { +// let poc_reward = radio_points.poc_reward(poc_rewards_per_share, speedtest_multiplier); + +// let hotspot_key: Vec = hotspot_key.clone().into(); +// let cbsd_id = cbsd_id.unwrap_or_default(); +// let poc_reward = poc_reward +// .round_dp_with_strategy(0, RoundingStrategy::ToZero) +// .to_u64() +// .unwrap_or(0); +// let boosted_hexes = radio_points +// .reward_points +// .iter() +// .filter(|radio_points| radio_points.boosted_hex.multiplier > NonZeroU32::new(1).unwrap()) +// .map(|radio_points| proto::BoostedHex { +// location: radio_points.boosted_hex.location.into_raw(), +// multiplier: radio_points.boosted_hex.multiplier.get(), +// }) +// .collect(); +// ( +// poc_reward, +// proto::MobileRewardShare { +// start_period, +// end_period, +// reward: Some(proto::mobile_reward_share::Reward::RadioReward( +// proto::RadioReward { +// hotspot_key, +// cbsd_id, +// poc_reward, +// coverage_points: radio_points.coverage_points().to_u64().unwrap_or(0), +// seniority_timestamp: radio_points.seniority.encode_timestamp(), +// coverage_object: Vec::from(radio_points.coverage_object.into_bytes()), +// location_trust_score_multiplier: (radio_points.location_trust_score_multiplier +// * dec!(1000)) +// .to_u32() +// .unwrap_or_default(), +// speedtest_multiplier: (speedtest_multiplier * dec!(1000)) +// .to_u32() +// .unwrap_or_default(), +// boosted_hexes, +// ..Default::default() +// }, +// )), +// }, +// ) +// } + pub fn get_total_scheduled_tokens(duration: Duration) -> Decimal { (TOTAL_EMISSIONS_POOL / dec!(366) / Decimal::from(Duration::hours(24).num_seconds())) * Decimal::from(duration.num_seconds()) @@ -695,12 +971,15 @@ pub fn get_scheduled_tokens_for_oracles(duration: Duration) -> Decimal { #[cfg(test)] mod test { + #![allow(unused)] + use super::*; use hex_assignments::{assignment::HexAssignments, Assignment}; + use mobile_config::boosted_hex_info::BoostedHex; use crate::{ cell_type::CellType, - coverage::{CoveredHexStream, HexCoverage, Seniority}, + coverage::{CoverageRewardPoints, CoveredHexStream, HexCoverage, Seniority}, data_session::{self, HotspotDataSession, HotspotReward}, heartbeats::{HeartbeatReward, KeyType, OwnedKeyType}, reward_shares, @@ -716,9 +995,118 @@ mod test { }; use hextree::Cell; use prost::Message; - use std::collections::HashMap; + use std::{collections::HashMap, num::NonZeroU32}; use uuid::Uuid; + #[derive(Debug)] + struct CoverageRewardPointsWithMultiplier { + coverage_points: CoverageRewardPoints, + boosted_hex: BoostedHex, + } + + #[derive(Debug)] + struct RadioPoints { + location_trust_score_multiplier: Decimal, + coverage_object: Uuid, + seniority: DateTime, + // Boosted hexes are included in CoverageRewardPointsWithMultiplier, + // this gets included in the radio reward share proto + reward_points: Vec, + } + + impl RadioPoints { + fn new( + location_trust_score_multiplier: Decimal, + coverage_object: Uuid, + seniority: DateTime, + ) -> Self { + Self { + location_trust_score_multiplier, + seniority, + coverage_object, + reward_points: vec![], + } + } + + fn hex_points(&self) -> Decimal { + self.reward_points + .iter() + .map(|c| c.coverage_points.points() * Decimal::from(c.boosted_hex.multiplier.get())) + .sum::() + } + + fn coverage_points(&self) -> Decimal { + let coverage_points = self.hex_points(); + (self.location_trust_score_multiplier * coverage_points).max(Decimal::ZERO) + } + + fn poc_reward(&self, reward_per_share: Decimal, speedtest_multiplier: Decimal) -> Decimal { + reward_per_share * speedtest_multiplier * self.coverage_points() + } + } + + // pub type HotspotBoostedHexes = HashMap; + + #[derive(Debug, Default)] + struct HotspotPoints { + /// Points are multiplied by the multiplier to get shares. + /// Multiplier should never be zero. + speedtest_multiplier: Decimal, + radio_points: HashMap, RadioPoints>, + } + + impl HotspotPoints { + pub fn add_coverage_entry( + &mut self, + radio_key: OwnedKeyType, + hotspot: PublicKeyBinary, + points: CoverageRewardPoints, + boosted_hex_info: BoostedHex, + verified_radio_thresholds: &VerifiedRadioThresholds, + ) { + let cbsd_id = radio_key.clone().into_cbsd_id(); + let rp = self.radio_points.get_mut(&cbsd_id).unwrap(); + // need to consider requirements from hip93 & hip84 before applying any boost + // hip93: if radio is wifi & location_trust score multiplier < 0.75, no boosting + // hip84: if radio has not met minimum data and subscriber thresholds, no boosting + let final_boost_info = if radio_key.is_wifi() + && rp.location_trust_score_multiplier < dec!(0.75) + || !verified_radio_thresholds.is_verified(hotspot, cbsd_id) + { + BoostedHex { + location: boosted_hex_info.location, + multiplier: NonZeroU32::new(1).unwrap(), + } + } else { + boosted_hex_info + }; + + rp.reward_points.push(CoverageRewardPointsWithMultiplier { + coverage_points: points, + boosted_hex: final_boost_info, + }); + } + } + + impl HotspotPoints { + pub fn new(speedtest_multiplier: Decimal) -> Self { + Self { + speedtest_multiplier, + radio_points: HashMap::new(), + } + } + } + + impl HotspotPoints { + pub fn total_points(&self) -> Decimal { + self.speedtest_multiplier + * self + .radio_points + .values() + .fold(Decimal::ZERO, |sum, radio| sum + radio.coverage_points()) + } + } + fn hex_assignments_mock() -> HexAssignments { HexAssignments { footfall: Assignment::A, @@ -1405,6 +1793,7 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) + .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1577,6 +1966,7 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) + .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1707,6 +2097,7 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) + .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1837,6 +2228,7 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) + .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1882,75 +2274,207 @@ mod test { let c2 = "P27-SCE4255W2107CW5000015".to_string(); let c3 = "2AG32PBS3101S1202000464223GY0153".to_string(); - let mut coverage_points = HashMap::new(); - - coverage_points.insert( - gw1.clone(), - HotspotPoints { - speedtest_multiplier: dec!(1.0), - radio_points: vec![( - Some(c1), - RadioPoints { - location_trust_score_multiplier: dec!(1.0), - seniority: DateTime::default(), - coverage_object: Uuid::new_v4(), - reward_points: vec![CoverageRewardPointsWithMultiplier { - coverage_points: CoverageRewardPoints { - boost_multiplier: NonZeroU32::new(1).unwrap(), - coverage_points: dec!(10.0), - hex_assignments: hex_assignments_mock(), - rank: None, + // let mut coverage_points = HashMap::new(); + + // coverage_points.insert( + // gw1.clone(), + // HotspotPoints { + // speedtest_multiplier: dec!(1.0), + // radio_points: vec![( + // Some(c1), + // RadioPoints { + // location_trust_score_multiplier: dec!(1.0), + // seniority: DateTime::default(), + // coverage_object: Uuid::new_v4(), + // reward_points: vec![CoverageRewardPointsWithMultiplier { + // coverage_points: CoverageRewardPoints { + // boost_multiplier: NonZeroU32::new(1).unwrap(), + // coverage_points: dec!(10.0), + // hex_assignments: hex_assignments_mock(), + // rank: None, + // }, + // boosted_hex: BoostedHex { + // location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), + // multiplier: NonZeroU32::new(1).unwrap(), + // }, + // }], + // }, + // )] + // .into_iter() + // .collect(), + // }, + // ); + // coverage_points.insert( + // gw2.clone(), + // HotspotPoints { + // speedtest_multiplier: dec!(1.0), + // radio_points: vec![ + // ( + // Some(c2), + // RadioPoints { + // location_trust_score_multiplier: dec!(1.0), + // seniority: DateTime::default(), + // coverage_object: Uuid::new_v4(), + // reward_points: vec![], + // }, + // ), + // ( + // Some(c3), + // RadioPoints { + // location_trust_score_multiplier: dec!(1.0), + // reward_points: vec![], + // seniority: DateTime::default(), + // coverage_object: Uuid::new_v4(), + // }, + // ), + // ] + // .into_iter() + // .collect(), + // }, + // ); + + let now = Utc::now(); + let epoch = now - Duration::hours(1)..now; + // We should never see any radio shares from owner2, since all of them are + // less than or equal to zero. + let speedtest_averages = SpeedtestAverages { + averages: HashMap::from_iter([ + ( + gw1.clone(), + SpeedtestAverage::from(vec![ + Speedtest { + report: CellSpeedtest { + pubkey: gw1.clone(), + serial: "serial-1".to_string(), + timestamp: now.clone(), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 10, }, - boosted_hex: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), + }, + Speedtest { + report: CellSpeedtest { + pubkey: gw1.clone(), + serial: "serial-1".to_string(), + timestamp: now.clone(), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 10, }, - }], - }, - )] - .into_iter() - .collect(), - }, - ); - coverage_points.insert( - gw2, - HotspotPoints { - speedtest_multiplier: dec!(1.0), - radio_points: vec![ - ( - Some(c2), - RadioPoints { - location_trust_score_multiplier: dec!(1.0), - seniority: DateTime::default(), - coverage_object: Uuid::new_v4(), - reward_points: vec![], }, - ), - ( - Some(c3), - RadioPoints { - location_trust_score_multiplier: dec!(1.0), - reward_points: vec![], - seniority: DateTime::default(), - coverage_object: Uuid::new_v4(), + ]), + ), + ( + gw2.clone(), + SpeedtestAverage::from(vec![Speedtest { + report: CellSpeedtest { + pubkey: gw2.clone(), + serial: "serial-2".to_string(), + timestamp: now.clone(), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 10, }, - ), - ] - .into_iter() - .collect(), - }, + }]), + ), + ]), + }; + + let uuid_1 = Uuid::new_v4(); + let uuid_2 = Uuid::new_v4(); + + let mut radio_heartbeats = HashMap::new(); + radio_heartbeats.insert( + (gw1.clone(), None), + vec![HeartbeatReward { + hotspot_key: gw1.clone(), + cbsd_id: None, + cell_type: CellType::Nova430I, + distances_to_asserted: None, + trust_score_multipliers: vec![dec!(1)], + coverage_object: uuid_1, + }], + ); + radio_heartbeats.insert( + (gw2.clone(), None), + vec![HeartbeatReward { + hotspot_key: gw2.clone(), + cbsd_id: None, + cell_type: CellType::Nova430I, + distances_to_asserted: None, + trust_score_multipliers: vec![dec!(1)], + coverage_object: uuid_2, + }], + ); + let mut coverage_map = HashMap::new(); + coverage_map.insert( + (gw1.clone(), None), + vec![HexCoverage { + uuid: uuid_1, + hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), + indoor: false, + radio_key: OwnedKeyType::Cbrs("key-1".to_string()), + signal_level: crate::coverage::SignalLevel::High, + signal_power: 42, + coverage_claim_time: now.clone(), + inserted_at: now.clone(), + assignments: hex_assignments_mock(), + }], + ); + coverage_map.insert( + (gw2.clone(), None), + vec![HexCoverage { + uuid: uuid_2, + hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), + indoor: false, + radio_key: OwnedKeyType::Cbrs("key-2".to_string()), + signal_level: crate::coverage::SignalLevel::High, + signal_power: 42, + coverage_claim_time: now.clone(), + inserted_at: now.clone(), + assignments: hex_assignments_mock(), + }], + ); + let mut seniority_timestamps = HashMap::new(); + seniority_timestamps.insert( + (gw1.clone(), None), + vec![Seniority { + uuid: Uuid::new_v4(), + seniority_ts: now.clone(), + last_heartbeat: now.clone(), + inserted_at: now.clone(), + update_reason: 0, + }], + ); + seniority_timestamps.insert( + (gw2.clone(), None), + vec![Seniority { + uuid: Uuid::new_v4(), + seniority_ts: now.clone(), + last_heartbeat: now.clone(), + inserted_at: now.clone(), + update_reason: 0, + }], ); - let now = Utc::now(); - // We should never see any radio shares from owner2, since all of them are - // less than or equal to zero. - let coverage_points = CoveragePoints { coverage_points }; - let epoch = now - Duration::hours(1)..now; + let coverage_points = CoveragePoints { + coverage_points: CoveragePoints2 { + boosted_hexes: BoostedHexes::default(), + verified_radio_thresholds: VerifiedRadioThresholds::default(), + speedtest_averages, + radio_heartbeats, + coverage_map, + reward_period: epoch.clone(), + seniority_timestamps, + }, + }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); + // gw2 does not have enough speedtests for a mulitplier let expected_hotspot = gw1; for (_reward_amount, mobile_reward) in coverage_points .into_rewards(total_poc_rewards, &epoch) - .unwrap() + .await + .expect("rewards output") { let radio_reward = match mobile_reward.reward { Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward, @@ -1963,15 +2487,24 @@ mod test { #[tokio::test] async fn skip_empty_radio_rewards() { + let now = Utc::now(); + let epoch = now - Duration::hours(1)..now; let coverage_points = CoveragePoints { - coverage_points: HashMap::new(), + coverage_points: CoveragePoints2 { + boosted_hexes: BoostedHexes::default(), + verified_radio_thresholds: VerifiedRadioThresholds::default(), + speedtest_averages: SpeedtestAverages::default(), + radio_heartbeats: HashMap::new(), + coverage_map: HashMap::new(), + reward_period: epoch.clone(), + seniority_timestamps: HashMap::new(), + }, }; - let now = Utc::now(); - let epoch = now - Duration::hours(1)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); assert!(coverage_points .into_rewards(total_poc_rewards, &epoch) + .await .is_none()); } diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index c9c3792b3..2ecc6934f 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -411,8 +411,9 @@ async fn reward_poc( ) .await?; - let unallocated_poc_amount = if let Some(mobile_reward_shares) = - coverage_points.into_rewards(total_poc_rewards, reward_period) + let unallocated_poc_amount = if let Some(mobile_reward_shares) = coverage_points + .into_rewards(total_poc_rewards, reward_period) + .await { // handle poc reward outputs let mut allocated_poc_rewards = 0_u64; From df04fdd3c2c7b59b9e9f9681ff06d5cf73a6fe49 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 10:44:39 -0700 Subject: [PATCH 04/40] add coverage map to compute rank --- mobile_verifier/src/coverage.rs | 20 +- mobile_verifier/src/reward_shares.rs | 420 ++++++++++----------------- 2 files changed, 168 insertions(+), 272 deletions(-) diff --git a/mobile_verifier/src/coverage.rs b/mobile_verifier/src/coverage.rs index ba0813a18..56e6ce315 100644 --- a/mobile_verifier/src/coverage.rs +++ b/mobile_verifier/src/coverage.rs @@ -373,11 +373,11 @@ pub struct HexCoverage { pub assignments: HexAssignments, } -#[derive(Eq, Debug)] -struct IndoorCoverageLevel { +#[derive(Eq, Debug, Clone)] +pub struct IndoorCoverageLevel { radio_key: OwnedKeyType, seniority_timestamp: DateTime, - hotspot: PublicKeyBinary, + pub hotspot: PublicKeyBinary, signal_level: SignalLevel, hex_assignments: HexAssignments, } @@ -412,11 +412,11 @@ impl IndoorCoverageLevel { } } -#[derive(Eq, Debug)] -struct OutdoorCoverageLevel { +#[derive(Eq, Debug, Clone)] +pub struct OutdoorCoverageLevel { radio_key: OwnedKeyType, seniority_timestamp: DateTime, - hotspot: PublicKeyBinary, + pub hotspot: PublicKeyBinary, signal_power: i32, signal_level: SignalLevel, hex_assignments: HexAssignments, @@ -627,10 +627,10 @@ type OutdoorCellTree = HashMap>; #[derive(Default, Debug)] pub struct CoveredHexes { - indoor_cbrs: IndoorCellTree, - indoor_wifi: IndoorCellTree, - outdoor_cbrs: OutdoorCellTree, - outdoor_wifi: OutdoorCellTree, + pub indoor_cbrs: IndoorCellTree, + pub indoor_wifi: IndoorCellTree, + pub outdoor_cbrs: OutdoorCellTree, + pub outdoor_wifi: OutdoorCellTree, } pub const MAX_INDOOR_RADIOS_PER_RES12_HEX: usize = 1; diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index f086f6379..6e17250ca 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1,5 +1,5 @@ use crate::{ - coverage::{CoveredHexStream, HexCoverage, Seniority}, + coverage::{CoveredHexStream, CoveredHexes, HexCoverage, Seniority}, data_session::{HotspotMap, ServiceProviderDataSession}, heartbeats::{HeartbeatReward, OwnedKeyType}, radio_threshold::VerifiedRadioThresholds, @@ -7,6 +7,7 @@ use crate::{ subscriber_location::SubscriberValidatedLocations, }; use chrono::{DateTime, Duration, Utc}; +use coverage_point_calculator::{CoverageMap, RewardableRadio}; use file_store::traits::TimestampEncode; use futures::{Stream, StreamExt}; use helium_crypto::PublicKeyBinary; @@ -25,7 +26,7 @@ use mobile_config::{ }; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -use std::{collections::HashMap, ops::Range}; +use std::{collections::HashMap, num::NonZeroUsize, ops::Range}; use uuid::Uuid; /// Total tokens emissions pool per 365 days or 366 days for a leap year @@ -376,9 +377,11 @@ pub fn dc_to_mobile_bones(dc_amount: Decimal, mobile_bone_price: Decimal) -> Dec } type RadioId = (PublicKeyBinary, Option); +// type RadioId = PublicKeyBinary; #[derive(Debug)] pub struct CoveragePoints2 { + covered_hexes: CoveredHexes, boosted_hexes: BoostedHexes, verified_radio_thresholds: VerifiedRadioThresholds, speedtest_averages: SpeedtestAverages, @@ -400,13 +403,14 @@ impl CoveragePoints2 { let mut radio_heartbeats: HashMap> = HashMap::new(); let mut coverage_map: HashMap> = HashMap::new(); let mut seniority_timestamps: HashMap> = HashMap::new(); + let mut covered_hexes = CoveredHexes::default(); let mut heartbeats = std::pin::pin!(heartbeats); while let Some(heartbeat) = heartbeats.next().await.transpose()? { let pubkey = heartbeat.hotspot_key.clone(); let heartbeat_key = heartbeat.key().clone(); let cbsd_id = heartbeat_key.to_owned().into_cbsd_id(); - let key = (pubkey, cbsd_id); + let key = (pubkey.clone(), cbsd_id); // * --- let period_end = reward_period.end; @@ -414,11 +418,11 @@ impl CoveragePoints2 { .fetch_seniority(heartbeat_key, period_end) .await?; { - let covered_hexes = hex_streams + let covered_hexes_stream = hex_streams .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) .await?; - let mut covered_hexes = std::pin::pin!(covered_hexes); + let mut covered_hexes = std::pin::pin!(covered_hexes_stream); while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { coverage_map .entry(key.clone()) @@ -426,6 +430,14 @@ impl CoveragePoints2 { .push(hex_coverage); } } + { + let covered_hexes_stream = hex_streams + .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) + .await?; + covered_hexes + .aggregate_coverage(&pubkey, &boosted_hexes, covered_hexes_stream) + .await?; + } seniority_timestamps .entry(key.clone()) @@ -440,6 +452,7 @@ impl CoveragePoints2 { } Ok(Self { + covered_hexes, boosted_hexes, verified_radio_thresholds, speedtest_averages, @@ -450,9 +463,7 @@ impl CoveragePoints2 { }) } - pub async fn all_radio_ids( - &mut self, - ) -> anyhow::Result)>> { + pub async fn all_radio_ids(&mut self) -> anyhow::Result> { Ok(self.radio_heartbeats.keys().cloned().collect()) } @@ -482,6 +493,81 @@ impl CoveragePoints2 { } } + fn radio_rank(&self, key: &RadioId, hex: &HexCoverage) -> NonZeroUsize { + match self.radio_type(key) { + coverage_point_calculator::RadioType::IndoorWifi => { + let hex_coverage = self + .covered_hexes + .indoor_wifi + .get(&hex.hex) + .expect("any indoor wifi radios covering a cell"); + let coverage_by_level = + hex_coverage + .clone() + .into_iter() + .flat_map(|(sig_level, coverage)| { + coverage + .into_sorted_vec() + .into_iter() + .map(move |cl| (sig_level, cl)) + }); + for (index, (_sig_level, coverage)) in coverage_by_level.enumerate() { + if coverage.hotspot == key.0 { + return NonZeroUsize::new(index + 1).unwrap(); + } + } + } + coverage_point_calculator::RadioType::OutdoorWifi => { + let coverage = self + .covered_hexes + .outdoor_wifi + .get(&hex.hex) + .expect("any outdoor wifi radios covering a cell"); + + for (index, cl) in coverage.clone().into_sorted_vec().iter().enumerate() { + if cl.hotspot == key.0 { + return NonZeroUsize::new(index + 1).unwrap(); + } + } + } + coverage_point_calculator::RadioType::IndoorCbrs => { + let hex_coverage = self + .covered_hexes + .indoor_cbrs + .get(&hex.hex) + .expect("any indoor cbrs radios covering a cell"); + let coverage_by_level = + hex_coverage + .clone() + .into_iter() + .flat_map(|(sig_level, coverage)| { + coverage + .into_sorted_vec() + .into_iter() + .map(move |cl| (sig_level, cl)) + }); + for (index, (_signal_level, coverage)) in coverage_by_level.enumerate() { + if coverage.hotspot == key.0 { + return NonZeroUsize::new(index + 1).unwrap(); + } + } + } + coverage_point_calculator::RadioType::OutdoorCbrs => { + let coverage = self + .covered_hexes + .outdoor_cbrs + .get(&hex.hex) + .expect("any outdoor cbrs radios covering cell"); + for (index, cl) in coverage.clone().into_sorted_vec().iter().enumerate() { + if cl.hotspot == key.0 { + return NonZeroUsize::new(index + 1).unwrap(); + } + } + } + } + return NonZeroUsize::MAX; + } + fn radio_threshold_verified( &self, key: &RadioId, @@ -506,42 +592,8 @@ impl CoveragePoints2 { &mut self, radio_id: &RadioId, ) -> anyhow::Result { - struct Temp { - radio_id: RadioId, - radio_type: coverage_point_calculator::RadioType, - speedtests: Vec, - trust_scores: Vec, - verified: coverage_point_calculator::SubscriberThreshold, - } - - impl coverage_point_calculator::Radio for Temp { - fn key(&self) -> RadioId { - self.radio_id.clone() - } - - fn radio_type(&self) -> coverage_point_calculator::RadioType { - self.radio_type.clone() - } - - fn speedtests(&self) -> Vec { - self.speedtests.clone() - } - - fn location_trust_scores( - &self, - ) -> Vec { - self.trust_scores.clone() - } - - fn verified_radio_threshold(&self) -> coverage_point_calculator::SubscriberThreshold { - self.verified.clone() - } - } - - let (pubkey, _cbsd_id) = &radio_id; - - // * speedtest let mut speedtests = vec![]; + let pubkey = &radio_id.0; let average = self.speedtest_averages.get_average(pubkey).unwrap(); for test in average.speedtests { use coverage_point_calculator::speedtest; @@ -552,9 +604,8 @@ impl CoveragePoints2 { }); } - // * trust scores let mut trust_scores = vec![]; - for heartbeat in self.radio_heartbeats.get(&radio_id).unwrap() { + for heartbeat in self.radio_heartbeats.get(&radio_id).unwrap_or(&vec![]) { use coverage_point_calculator::location; // FIXME: what do we do for radios with no asserted distances? @@ -577,76 +628,27 @@ impl CoveragePoints2 { } } - let temp = Temp { - radio_id: radio_id.clone(), - radio_type: self.radio_type(&radio_id), + let radio = RewardableRadio::new( + self.radio_type(&radio_id), speedtests, trust_scores, - verified: self.radio_threshold_verified(&radio_id), - }; - let radio = coverage_point_calculator::make_rewardable_radio(&temp, self); - Ok(coverage_point_calculator::calculate_coverage_points(radio)) - } -} + self.radio_threshold_verified(&radio_id), + self.hexes(&radio_id), + ); -pub fn coverage_point_to_mobile_reward_share( - coverage_points: coverage_point_calculator::CoveragePoints, - start_period: u64, - end_period: u64, - hotspot_key: &PublicKeyBinary, - cbsd_id: Option, - poc_reward: u64, - seniority_timestamp: DateTime, - coverage_object_uuid: Uuid, -) -> proto::MobileRewardShare { - println!("\nBoosted Hexes:"); - println!(" radio: {hotspot_key}"); - let boosted_hexes = coverage_points - .radio - .boosted_hexes() - .map(|covered_hex| proto::BoostedHex { - location: covered_hex.cell.into_raw(), - multiplier: covered_hex.boosted.unwrap().into(), - }) - .collect::>(); - println!(" boosted count: {}", boosted_hexes.len()); + let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); - proto::MobileRewardShare { - start_period, - end_period, - reward: Some(proto::mobile_reward_share::Reward::RadioReward( - proto::RadioReward { - hotspot_key: hotspot_key.clone().into(), - cbsd_id: cbsd_id.unwrap_or_default(), - poc_reward, - coverage_points: coverage_points - .radio - .hex_coverage_points() - .to_u64() - .unwrap_or(0), - seniority_timestamp: seniority_timestamp.encode_timestamp(), - coverage_object: Vec::from(coverage_object_uuid.into_bytes()), - location_trust_score_multiplier: coverage_points - .reward_share_location_trust_multiplier(), - speedtest_multiplier: coverage_points.reward_share_speedtest_multiplier(), - boosted_hexes, - ..Default::default() - }, - )), + Ok(coverage_points) } } impl coverage_point_calculator::CoverageMap for CoveragePoints2 { - fn hexes( - &self, - radio: &impl coverage_point_calculator::Radio, - ) -> Vec { + fn hexes(&self, radio_id: &RadioId) -> Vec { use crate::coverage; use coverage_point_calculator as cpc; - let key = radio.key(); self.coverage_map - .get(&key) + .get(radio_id) .expect("coverage map") .into_iter() .map(|hex| { @@ -655,8 +657,7 @@ impl coverage_point_calculator::CoverageMap for CoveragePoints2 { .get_current_multiplier(hex.hex, self.reward_period.start); coverage_point_calculator::CoveredHex { cell: hex.hex, - // TODO: fix the rank - rank: coverage_point_calculator::Rank::new(1).unwrap(), + rank: self.radio_rank(&radio_id, &hex), signal_level: match hex.signal_level { coverage::SignalLevel::None => cpc::SignalLevel::None, coverage::SignalLevel::Low => cpc::SignalLevel::Low, @@ -687,6 +688,50 @@ impl coverage_point_calculator::CoverageMap for CoveragePoints2 { } } +pub fn coverage_point_to_mobile_reward_share( + coverage_points: coverage_point_calculator::CoveragePoints, + start_period: u64, + end_period: u64, + hotspot_key: &PublicKeyBinary, + cbsd_id: Option, + poc_reward: u64, + seniority_timestamp: DateTime, + coverage_object_uuid: Uuid, +) -> proto::MobileRewardShare { + let boosted_hexes = coverage_points + .radio + .boosted_hexes() + .map(|covered_hex| proto::BoostedHex { + location: covered_hex.cell.into_raw(), + multiplier: covered_hex.boosted.unwrap().into(), + }) + .collect(); + + proto::MobileRewardShare { + start_period, + end_period, + reward: Some(proto::mobile_reward_share::Reward::RadioReward( + proto::RadioReward { + hotspot_key: hotspot_key.clone().into(), + cbsd_id: cbsd_id.unwrap_or_default(), + poc_reward, + coverage_points: coverage_points + .radio + .hex_coverage_points() + .to_u64() + .unwrap_or(0), + seniority_timestamp: seniority_timestamp.encode_timestamp(), + coverage_object: Vec::from(coverage_object_uuid.into_bytes()), + location_trust_score_multiplier: coverage_points + .reward_share_location_trust_multiplier(), + speedtest_multiplier: coverage_points.reward_share_speedtest_multiplier(), + boosted_hexes, + ..Default::default() + }, + )), + } +} + #[derive(Debug)] pub struct CoveragePoints { // coverage_points: HashMap, @@ -712,67 +757,15 @@ impl CoveragePoints { ) .await?; Ok(Self { coverage_points }) - // let mut heartbeats = std::pin::pin!(heartbeats); - // let mut covered_hexes = CoveredHexes::default(); - // let mut coverage_points = HashMap::new(); - // while let Some(heartbeat) = heartbeats.next().await.transpose()? { - // let speedtest_multiplier = speedtests - // .get_average(&heartbeat.hotspot_key) - // .as_ref() - // .map_or(Decimal::ZERO, SpeedtestAverage::reward_multiplier); - // let seniority = hex_streams - // .fetch_seniority(heartbeat.key(), reward_period.end) - // .await?; - // let covered_hex_stream = hex_streams - // .covered_hex_stream(heartbeat.key(), &heartbeat.coverage_object, &seniority) - // .await?; - // let overlaps_boosted = covered_hexes - // .aggregate_coverage(&heartbeat.hotspot_key, boosted_hexes, covered_hex_stream) - // .await?; - // let opt_cbsd_id = heartbeat.key().to_owned().into_cbsd_id(); - // coverage_points - // .entry(heartbeat.hotspot_key.clone()) - // .or_insert_with(|| HotspotPoints::new(speedtest_multiplier)) - // .radio_points - // .insert( - // opt_cbsd_id, - // RadioPoints::new( - // heartbeat.trust_score_multiplier(overlaps_boosted), - // heartbeat.coverage_object, - // seniority.seniority_ts, - // ), - // ); - // } - - // for CoverageReward { - // radio_key, - // points, - // hotspot, - // boosted_hex_info, - // } in covered_hexes.into_coverage_rewards(boosted_hexes, reward_period.start) - // { - // // Guaranteed that points contains the given hotspot. - // coverage_points - // .get_mut(&hotspot) - // .unwrap() - // .add_coverage_entry( - // radio_key, - // hotspot, - // points, - // boosted_hex_info, - // verified_radio_thresholds, - // ) - // } - // Ok(Self { coverage_points }) } /// Only used for testing - pub fn hotspot_points(&self, _hotspot: &PublicKeyBinary) -> Decimal { - todo!() - // self.coverage_points - // .get(hotspot) - // .map(HotspotPoints::total_points) - // .unwrap_or(Decimal::ZERO) + pub async fn hotspot_points(&mut self, hotspot: &RadioId) -> Decimal { + self.coverage_points + .coverage_points(hotspot) + .await + .expect("coverage points for hotspot") + .coverage_points } pub fn total_shares(&self) -> Decimal { @@ -789,7 +782,6 @@ impl CoveragePoints { available_poc_rewards: Decimal, epoch: &'_ Range>, ) -> Option + '_> { - println!("-- available poc rewards: {available_poc_rewards}"); let radio_ids = self .coverage_points .all_radio_ids() @@ -847,107 +839,9 @@ impl CoveragePoints { }) .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0), ) - - // let total_shares = self.total_shares(); - // available_poc_rewards - // .checked_div(total_shares) - // .map(|poc_rewards_per_share| { - // tracing::info!(%poc_rewards_per_share); - // let start_period = epoch.start.encode_timestamp(); - // let end_period = epoch.end.encode_timestamp(); - // self.coverage_points - // .into_iter() - // .flat_map(move |(hotspot_key, hotspot_points)| { - // radio_points_into_rewards( - // hotspot_key, - // start_period, - // end_period, - // poc_rewards_per_share, - // hotspot_points.speedtest_multiplier, - // hotspot_points.radio_points.into_iter(), - // ) - // }) - // .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0) - // }) } } -// fn radio_points_into_rewards( -// hotspot_key: PublicKeyBinary, -// start_period: u64, -// end_period: u64, -// poc_rewards_per_share: Decimal, -// speedtest_multiplier: Decimal, -// radio_points: impl Iterator, RadioPoints)>, -// ) -> impl Iterator { -// radio_points.map(move |(cbsd_id, radio_points)| { -// new_radio_reward( -// cbsd_id, -// &hotspot_key, -// start_period, -// end_period, -// poc_rewards_per_share, -// speedtest_multiplier, -// radio_points, -// ) -// }) -// } - -// #[allow(clippy::too_many_arguments)] -// fn new_radio_reward( -// cbsd_id: Option, -// hotspot_key: &PublicKeyBinary, -// start_period: u64, -// end_period: u64, -// poc_rewards_per_share: Decimal, -// speedtest_multiplier: Decimal, -// radio_points: RadioPoints, -// ) -> (u64, proto::MobileRewardShare) { -// let poc_reward = radio_points.poc_reward(poc_rewards_per_share, speedtest_multiplier); - -// let hotspot_key: Vec = hotspot_key.clone().into(); -// let cbsd_id = cbsd_id.unwrap_or_default(); -// let poc_reward = poc_reward -// .round_dp_with_strategy(0, RoundingStrategy::ToZero) -// .to_u64() -// .unwrap_or(0); -// let boosted_hexes = radio_points -// .reward_points -// .iter() -// .filter(|radio_points| radio_points.boosted_hex.multiplier > NonZeroU32::new(1).unwrap()) -// .map(|radio_points| proto::BoostedHex { -// location: radio_points.boosted_hex.location.into_raw(), -// multiplier: radio_points.boosted_hex.multiplier.get(), -// }) -// .collect(); -// ( -// poc_reward, -// proto::MobileRewardShare { -// start_period, -// end_period, -// reward: Some(proto::mobile_reward_share::Reward::RadioReward( -// proto::RadioReward { -// hotspot_key, -// cbsd_id, -// poc_reward, -// coverage_points: radio_points.coverage_points().to_u64().unwrap_or(0), -// seniority_timestamp: radio_points.seniority.encode_timestamp(), -// coverage_object: Vec::from(radio_points.coverage_object.into_bytes()), -// location_trust_score_multiplier: (radio_points.location_trust_score_multiplier -// * dec!(1000)) -// .to_u32() -// .unwrap_or_default(), -// speedtest_multiplier: (speedtest_multiplier * dec!(1000)) -// .to_u32() -// .unwrap_or_default(), -// boosted_hexes, -// ..Default::default() -// }, -// )), -// }, -// ) -// } - pub fn get_total_scheduled_tokens(duration: Duration) -> Decimal { (TOTAL_EMISSIONS_POOL / dec!(366) / Decimal::from(Duration::hours(24).num_seconds())) * Decimal::from(duration.num_seconds()) @@ -2459,6 +2353,7 @@ mod test { let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { + covered_hexes: CoveredHexes::default(), boosted_hexes: BoostedHexes::default(), verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages, @@ -2491,6 +2386,7 @@ mod test { let epoch = now - Duration::hours(1)..now; let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { + covered_hexes: CoveredHexes::default(), boosted_hexes: BoostedHexes::default(), verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages: SpeedtestAverages::default(), From e297687247d9d563a3df404695151ae30b68e991 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 10:45:29 -0700 Subject: [PATCH 05/40] unignore mobile verifier integration tests --- .../tests/integrations/heartbeats.rs | 7 - .../tests/integrations/modeled_coverage.rs | 145 ++++++++++++++---- .../tests/integrations/seniority.rs | 1 - 3 files changed, 111 insertions(+), 42 deletions(-) diff --git a/mobile_verifier/tests/integrations/heartbeats.rs b/mobile_verifier/tests/integrations/heartbeats.rs index d79f259d5..c827e9f44 100644 --- a/mobile_verifier/tests/integrations/heartbeats.rs +++ b/mobile_verifier/tests/integrations/heartbeats.rs @@ -12,7 +12,6 @@ use sqlx::PgPool; use uuid::Uuid; #[sqlx::test] -#[ignore] async fn test_save_wifi_heartbeat(pool: PgPool) -> anyhow::Result<()> { let coverage_object = Uuid::new_v4(); let heartbeat = ValidatedHeartbeat { @@ -50,7 +49,6 @@ async fn test_save_wifi_heartbeat(pool: PgPool) -> anyhow::Result<()> { } #[sqlx::test] -#[ignore] async fn test_save_cbrs_heartbeat(pool: PgPool) -> anyhow::Result<()> { let coverage_object = Uuid::new_v4(); let heartbeat = ValidatedHeartbeat { @@ -88,7 +86,6 @@ async fn test_save_cbrs_heartbeat(pool: PgPool) -> anyhow::Result<()> { } #[sqlx::test] -#[ignore] async fn only_fetch_latest_hotspot(pool: PgPool) -> anyhow::Result<()> { let cbsd_id = "P27-SCE4255W120200039521XGB0103".to_string(); let coverage_object = Uuid::new_v4(); @@ -156,7 +153,6 @@ VALUES } #[sqlx::test] -#[ignore] async fn ensure_hotspot_does_not_affect_count(pool: PgPool) -> anyhow::Result<()> { let cbsd_id = "P27-SCE4255W120200039521XGB0103".to_string(); let coverage_object = Uuid::new_v4(); @@ -212,7 +208,6 @@ VALUES } #[sqlx::test] -#[ignore] async fn ensure_minimum_count(pool: PgPool) -> anyhow::Result<()> { let cbsd_id = "P27-SCE4255W120200039521XGB0103".to_string(); let coverage_object = Uuid::new_v4(); @@ -253,7 +248,6 @@ VALUES } #[sqlx::test] -#[ignore] async fn ensure_wifi_hotspots_are_rewarded(pool: PgPool) -> anyhow::Result<()> { let early_coverage_object = Uuid::new_v4(); let latest_coverage_object = Uuid::new_v4(); @@ -305,7 +299,6 @@ VALUES } #[sqlx::test] -#[ignore] async fn ensure_wifi_hotspots_use_average_location_trust_score(pool: PgPool) -> anyhow::Result<()> { let early_coverage_object = Uuid::new_v4(); let latest_coverage_object = Uuid::new_v4(); diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index b59b5e6e5..8954f2c7a 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -48,7 +48,6 @@ const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU"; const BOOST_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG"; #[sqlx::test] -#[ignore] async fn test_save_wifi_coverage_object(pool: PgPool) -> anyhow::Result<()> { let cache = CoverageObjectCache::new(&pool); let uuid = Uuid::new_v4(); @@ -116,7 +115,6 @@ async fn test_save_wifi_coverage_object(pool: PgPool) -> anyhow::Result<()> { } #[sqlx::test] -#[ignore] async fn test_save_cbrs_coverage_object(pool: PgPool) -> anyhow::Result<()> { let cache = CoverageObjectCache::new(&pool); let uuid = Uuid::new_v4(); @@ -180,7 +178,6 @@ async fn test_save_cbrs_coverage_object(pool: PgPool) -> anyhow::Result<()> { } #[sqlx::test] -#[ignore] async fn test_coverage_object_save_updates(pool: PgPool) -> anyhow::Result<()> { let cache = CoverageObjectCache::new(&pool); let uuid = Uuid::new_v4(); @@ -448,7 +445,6 @@ async fn process_input( } #[sqlx::test] -#[ignore] async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-01-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-01-02 00:00:00.000000000 UTC".parse()?; @@ -496,7 +492,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -506,13 +502,17 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { ) .await?; - assert_eq!(coverage_points.hotspot_points(&owner), dec!(250)); + assert_eq!( + coverage_points + .hotspot_points(&(owner, Some(cbsd_id))) + .await, + dec!(250) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-02-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-02-02 00:00:00.000000000 UTC".parse()?; @@ -596,7 +596,7 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -606,14 +606,23 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { ) .await?; - assert_eq!(coverage_points.hotspot_points(&owner_1), dec!(112.5)); - assert_eq!(coverage_points.hotspot_points(&owner_2), dec!(250)); + assert_eq!( + coverage_points + .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .await, + dec!(112.5) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .await, + dec!(250) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-02-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-02-02 00:00:00.000000000 UTC".parse()?; @@ -879,7 +888,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -889,18 +898,47 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { ) .await?; - assert_eq!(coverage_points.hotspot_points(&owner_1), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_2), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_3), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_4), dec!(250)); - assert_eq!(coverage_points.hotspot_points(&owner_5), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_6), dec!(0)); + assert_eq!( + coverage_points + .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_3, Some(cbsd_id_3))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_4, Some(cbsd_id_4))) + .await, + dec!(250) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_5, Some(cbsd_id_5))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_6, Some(cbsd_id_6))) + .await, + dec!(0) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-01-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-01-02 00:00:00.000000000 UTC".parse()?; @@ -951,7 +989,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -961,13 +999,17 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { ) .await?; - assert_eq!(coverage_points.hotspot_points(&owner), dec!(19)); + assert_eq!( + coverage_points + .hotspot_points(&(owner, Some(cbsd_id))) + .await, + dec!(19) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-02-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-02-02 00:00:00.000000000 UTC".parse()?; @@ -1050,7 +1092,7 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -1061,16 +1103,22 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.hotspot_points(&owner_1), + coverage_points + .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .await, dec!(19) * dec!(0.5) ); - assert_eq!(coverage_points.hotspot_points(&owner_2), dec!(8)); + assert_eq!( + coverage_points + .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .await, + dec!(8) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let start: DateTime = "2022-02-01 00:00:00.000000000 UTC".parse()?; let end: DateTime = "2022-02-02 00:00:00.000000000 UTC".parse()?; @@ -1297,7 +1345,7 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let mut coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -1307,18 +1355,47 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { ) .await?; - assert_eq!(coverage_points.hotspot_points(&owner_1), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_2), dec!(62.5)); - assert_eq!(coverage_points.hotspot_points(&owner_3), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_4), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_5), dec!(0)); - assert_eq!(coverage_points.hotspot_points(&owner_6), dec!(0)); + assert_eq!( + coverage_points + .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .await, + dec!(62.5) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_3, Some(cbsd_id_3))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_4, Some(cbsd_id_4))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_5, Some(cbsd_id_5))) + .await, + dec!(0) + ); + assert_eq!( + coverage_points + .hotspot_points(&(owner_6, Some(cbsd_id_6))) + .await, + dec!(0) + ); Ok(()) } #[sqlx::test] -#[ignore] async fn ensure_lower_trust_score_for_distant_heartbeats(pool: PgPool) -> anyhow::Result<()> { let owner_1: PublicKeyBinary = "11xtYwQYnvkFYnJ9iZ8kmnetYKwhdi87Mcr36e1pVLrhBMPLjV9" .parse() diff --git a/mobile_verifier/tests/integrations/seniority.rs b/mobile_verifier/tests/integrations/seniority.rs index e3f1950dd..54bb59605 100644 --- a/mobile_verifier/tests/integrations/seniority.rs +++ b/mobile_verifier/tests/integrations/seniority.rs @@ -10,7 +10,6 @@ use sqlx::PgPool; use uuid::Uuid; #[sqlx::test] -#[ignore] async fn test_seniority_updates(pool: PgPool) -> anyhow::Result<()> { let coverage_object = Uuid::new_v4(); let mut heartbeat = ValidatedHeartbeat { From 0f136a0ca91acfc5a9bde0d708027716c3918ce6 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 14:48:52 -0700 Subject: [PATCH 06/40] update with rename SubscriberThreshold -> RadioThreshold thresholds being met for a radio are not only about subscribers. --- mobile_verifier/src/reward_shares.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 6e17250ca..2ee9656e9 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -568,16 +568,13 @@ impl CoveragePoints2 { return NonZeroUsize::MAX; } - fn radio_threshold_verified( - &self, - key: &RadioId, - ) -> coverage_point_calculator::SubscriberThreshold { + fn radio_threshold_verified(&self, key: &RadioId) -> coverage_point_calculator::RadioThreshold { match self .verified_radio_thresholds .is_verified(key.0.clone(), key.1.clone()) { - true => coverage_point_calculator::SubscriberThreshold::Verified, - false => coverage_point_calculator::SubscriberThreshold::UnVerified, + true => coverage_point_calculator::RadioThreshold::Verified, + false => coverage_point_calculator::RadioThreshold::UnVerified, } } From 3f554d324adcb3e5669c85e9d8fc4c910c87da01 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 14:49:43 -0700 Subject: [PATCH 07/40] filter for boosted hexes during report generation It's the responsibility of the report to only include hexes with active boost values --- mobile_verifier/src/reward_shares.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 2ee9656e9..570ef973a 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -695,9 +695,14 @@ pub fn coverage_point_to_mobile_reward_share( seniority_timestamp: DateTime, coverage_object_uuid: Uuid, ) -> proto::MobileRewardShare { + let radio_verified = coverage_points.radio.radio_threshold_met(); + let eligible_for_boosted = coverage_points.radio.eligible_for_boosted_hexes(); let boosted_hexes = coverage_points .radio - .boosted_hexes() + .iter_covered_hexes() + .filter(move |_| radio_verified) + .filter(move |_| eligible_for_boosted) + .filter(|covered_hex| covered_hex.is_boosted()) .map(|covered_hex| proto::BoostedHex { location: covered_hex.cell.into_raw(), multiplier: covered_hex.boosted.unwrap().into(), From 8af4dfcbe8261d5d5928ad3ab48137836ffa0704 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 17:48:48 -0700 Subject: [PATCH 08/40] calculator uses hex_assignments crate --- mobile_verifier/src/reward_shares.rs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 570ef973a..b9b8fb292 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -661,23 +661,7 @@ impl coverage_point_calculator::CoverageMap for CoveragePoints2 { coverage::SignalLevel::Medium => cpc::SignalLevel::Medium, coverage::SignalLevel::High => cpc::SignalLevel::High, }, - assignments: coverage_point_calculator::Assignments { - footfall: match hex.assignments.footfall { - hex_assignments::Assignment::A => cpc::Assignment::A, - hex_assignments::Assignment::B => cpc::Assignment::B, - hex_assignments::Assignment::C => cpc::Assignment::C, - }, - landtype: match hex.assignments.landtype { - hex_assignments::Assignment::A => cpc::Assignment::A, - hex_assignments::Assignment::B => cpc::Assignment::B, - hex_assignments::Assignment::C => cpc::Assignment::C, - }, - urbanized: match hex.assignments.urbanized { - hex_assignments::Assignment::A => cpc::Assignment::A, - hex_assignments::Assignment::B => cpc::Assignment::B, - hex_assignments::Assignment::C => cpc::Assignment::C, - }, - }, + assignments: hex.assignments.clone(), boosted: boosted_hex, } }) From dc87785f0c0ca3c4f017bd3bdd9b80aa0fcd6faa Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 31 May 2024 18:00:44 -0700 Subject: [PATCH 09/40] clean values for reports --- mobile_verifier/src/reward_shares.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index b9b8fb292..fac86f797 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -693,6 +693,14 @@ pub fn coverage_point_to_mobile_reward_share( }) .collect(); + let location_multiplier = coverage_points.radio.location_trust_multiplier(); + let speedtest_multiplier = coverage_points.radio.speedtest_multiplier(); + + let to_proto_value = |value: Decimal| (value * dec!(1000)).to_u32().unwrap_or_default(); + + // Does not include location and speedtest multipliers + let hex_coverage_points = coverage_points.radio.hex_coverage_points(); + proto::MobileRewardShare { start_period, end_period, @@ -701,16 +709,11 @@ pub fn coverage_point_to_mobile_reward_share( hotspot_key: hotspot_key.clone().into(), cbsd_id: cbsd_id.unwrap_or_default(), poc_reward, - coverage_points: coverage_points - .radio - .hex_coverage_points() - .to_u64() - .unwrap_or(0), + coverage_points: hex_coverage_points.to_u64().unwrap_or(0), seniority_timestamp: seniority_timestamp.encode_timestamp(), coverage_object: Vec::from(coverage_object_uuid.into_bytes()), - location_trust_score_multiplier: coverage_points - .reward_share_location_trust_multiplier(), - speedtest_multiplier: coverage_points.reward_share_speedtest_multiplier(), + location_trust_score_multiplier: to_proto_value(location_multiplier), + speedtest_multiplier: to_proto_value(speedtest_multiplier), boosted_hexes, ..Default::default() }, From 9a78c7238b023cf90b40bb257672e8dbaeb18ffb Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Mon, 3 Jun 2024 12:21:24 -0700 Subject: [PATCH 10/40] Start adding coverage-map in mobile-verifier Keep disctinction between SignalLevel in coverage and coverage-map, this way coverage-map doesn't need to bring in sqlx as a dependency --- Cargo.lock | 1 + mobile_verifier/Cargo.toml | 1 + mobile_verifier/src/coverage.rs | 11 +++++++++++ mobile_verifier/src/reward_shares.rs | 10 +--------- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 402796031..1d011e334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4749,6 +4749,7 @@ dependencies = [ "chrono", "clap 4.4.8", "config", + "coverage-map", "coverage-point-calculator", "custom-tracing", "db-store", diff --git a/mobile_verifier/Cargo.toml b/mobile_verifier/Cargo.toml index 217b1a80a..cc80ba81a 100644 --- a/mobile_verifier/Cargo.toml +++ b/mobile_verifier/Cargo.toml @@ -59,6 +59,7 @@ humantime-serde = { workspace = true } custom-tracing = { path = "../custom_tracing" } hex-assignments = { path = "../hex_assignments" } coverage-point-calculator = { path = "../coverage_point_calculator" } +coverage-map = { path = "../coverage_map" } [dev-dependencies] backon = "0" diff --git a/mobile_verifier/src/coverage.rs b/mobile_verifier/src/coverage.rs index 56e6ce315..94cb1dbb6 100644 --- a/mobile_verifier/src/coverage.rs +++ b/mobile_verifier/src/coverage.rs @@ -65,6 +65,17 @@ impl From for SignalLevel { } } +impl From for coverage_map::SignalLevel { + fn from(value: SignalLevel) -> Self { + match value { + SignalLevel::None => coverage_map::SignalLevel::None, + SignalLevel::Low => coverage_map::SignalLevel::Low, + SignalLevel::Medium => coverage_map::SignalLevel::Medium, + SignalLevel::High => coverage_map::SignalLevel::High, + } + } +} + pub struct CoverageDaemon { pool: Pool, auth_client: AuthorizationClient, diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index fac86f797..c4958c23b 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -641,9 +641,6 @@ impl CoveragePoints2 { impl coverage_point_calculator::CoverageMap for CoveragePoints2 { fn hexes(&self, radio_id: &RadioId) -> Vec { - use crate::coverage; - use coverage_point_calculator as cpc; - self.coverage_map .get(radio_id) .expect("coverage map") @@ -655,12 +652,7 @@ impl coverage_point_calculator::CoverageMap for CoveragePoints2 { coverage_point_calculator::CoveredHex { cell: hex.hex, rank: self.radio_rank(&radio_id, &hex), - signal_level: match hex.signal_level { - coverage::SignalLevel::None => cpc::SignalLevel::None, - coverage::SignalLevel::Low => cpc::SignalLevel::Low, - coverage::SignalLevel::Medium => cpc::SignalLevel::Medium, - coverage::SignalLevel::High => cpc::SignalLevel::High, - }, + signal_level: hex.signal_level.into(), assignments: hex.assignments.clone(), boosted: boosted_hex, } From f80434a712781d18b5e4f6858983ed6cbe60014e Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Tue, 4 Jun 2024 11:51:45 -0700 Subject: [PATCH 11/40] break out coverage map for drop in replacement testing --- mobile_verifier/src/reward_shares.rs | 598 ++++++++++++++++----------- 1 file changed, 351 insertions(+), 247 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index c4958c23b..2d49c9f0f 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1,15 +1,15 @@ use crate::{ coverage::{CoveredHexStream, CoveredHexes, HexCoverage, Seniority}, data_session::{HotspotMap, ServiceProviderDataSession}, - heartbeats::{HeartbeatReward, OwnedKeyType}, + heartbeats::HeartbeatReward, radio_threshold::VerifiedRadioThresholds, speedtests_average::SpeedtestAverages, subscriber_location::SubscriberValidatedLocations, }; use chrono::{DateTime, Duration, Utc}; -use coverage_point_calculator::{CoverageMap, RewardableRadio}; +use coverage_point_calculator::{location, CoverageMapExt, RewardableRadio}; use file_store::traits::TimestampEncode; -use futures::{Stream, StreamExt}; +use futures::{stream, Stream, StreamExt}; use helium_crypto::PublicKeyBinary; use helium_proto::{ services::{ @@ -380,121 +380,23 @@ type RadioId = (PublicKeyBinary, Option); // type RadioId = PublicKeyBinary; #[derive(Debug)] -pub struct CoveragePoints2 { +struct InnerCoverageMap { + coverage_hash_map: HashMap>, covered_hexes: CoveredHexes, boosted_hexes: BoostedHexes, - verified_radio_thresholds: VerifiedRadioThresholds, - speedtest_averages: SpeedtestAverages, - radio_heartbeats: HashMap>, - coverage_map: HashMap>, reward_period: Range>, - seniority_timestamps: HashMap>, + // in the end, we should be able to swap out the above members with the coverage_map crate. + coverage_map: coverage_map::CoverageMap, } -impl CoveragePoints2 { - pub async fn new( - hex_streams: &impl CoveredHexStream, - heartbeats: impl Stream>, - speedtest_averages: SpeedtestAverages, - boosted_hexes: BoostedHexes, - verified_radio_thresholds: VerifiedRadioThresholds, - reward_period: &Range>, - ) -> anyhow::Result { - let mut radio_heartbeats: HashMap> = HashMap::new(); - let mut coverage_map: HashMap> = HashMap::new(); - let mut seniority_timestamps: HashMap> = HashMap::new(); - let mut covered_hexes = CoveredHexes::default(); - - let mut heartbeats = std::pin::pin!(heartbeats); - while let Some(heartbeat) = heartbeats.next().await.transpose()? { - let pubkey = heartbeat.hotspot_key.clone(); - let heartbeat_key = heartbeat.key().clone(); - let cbsd_id = heartbeat_key.to_owned().into_cbsd_id(); - let key = (pubkey.clone(), cbsd_id); - - // * --- - let period_end = reward_period.end; - let seniority = hex_streams - .fetch_seniority(heartbeat_key, period_end) - .await?; - { - let covered_hexes_stream = hex_streams - .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) - .await?; - - let mut covered_hexes = std::pin::pin!(covered_hexes_stream); - while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { - coverage_map - .entry(key.clone()) - .or_default() - .push(hex_coverage); - } - } - { - let covered_hexes_stream = hex_streams - .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) - .await?; - covered_hexes - .aggregate_coverage(&pubkey, &boosted_hexes, covered_hexes_stream) - .await?; - } - - seniority_timestamps - .entry(key.clone()) - .or_default() - .push(seniority); - - // * --- - radio_heartbeats - .entry(key) - .or_default() - .push(heartbeat.clone()); - } - - Ok(Self { - covered_hexes, - boosted_hexes, - verified_radio_thresholds, - speedtest_averages, - radio_heartbeats, - coverage_map, - seniority_timestamps, - reward_period: reward_period.clone(), - }) - } - - pub async fn all_radio_ids(&mut self) -> anyhow::Result> { - Ok(self.radio_heartbeats.keys().cloned().collect()) - } - - pub fn get_seniority(&self, key: &RadioId) -> Option { - let mut s = self - .seniority_timestamps - .get(key) - .expect("seniority timestamps") - .clone(); - s.sort_by_key(|f| f.seniority_ts); - s.first().cloned() - } - - fn radio_type(&self, key: &RadioId) -> coverage_point_calculator::RadioType { - let hex_coverage = self - .coverage_map - .get(key) - .expect("coverage map entry for radio_type") - .first() - .expect("at least one hex coverage for radio type"); - - match (hex_coverage.indoor, &hex_coverage.radio_key) { - (true, OwnedKeyType::Cbrs(_)) => coverage_point_calculator::RadioType::IndoorCbrs, - (true, OwnedKeyType::Wifi(_)) => coverage_point_calculator::RadioType::IndoorWifi, - (false, OwnedKeyType::Cbrs(_)) => coverage_point_calculator::RadioType::OutdoorCbrs, - (false, OwnedKeyType::Wifi(_)) => coverage_point_calculator::RadioType::OutdoorWifi, - } - } - - fn radio_rank(&self, key: &RadioId, hex: &HexCoverage) -> NonZeroUsize { - match self.radio_type(key) { +impl InnerCoverageMap { + fn radio_rank( + &self, + key: &RadioId, + radio_type: &coverage_point_calculator::RadioType, + hex: &HexCoverage, + ) -> NonZeroUsize { + match radio_type { coverage_point_calculator::RadioType::IndoorWifi => { let hex_coverage = self .covered_hexes @@ -567,6 +469,173 @@ impl CoveragePoints2 { } return NonZeroUsize::MAX; } +} + +#[derive(Debug)] +pub struct CoveragePoints2 { + coverage_map: InnerCoverageMap, + // + verified_radio_thresholds: VerifiedRadioThresholds, + speedtest_averages: SpeedtestAverages, + trust_scores: HashMap>, + seniority_timestamps: HashMap>, + radio_types: HashMap, + coverage_obj_uuid: HashMap, +} + +impl CoveragePoints2 { + pub async fn new( + hex_streams: &impl CoveredHexStream, + heartbeats: impl Stream>, + speedtest_averages: SpeedtestAverages, + boosted_hexes: BoostedHexes, + verified_radio_thresholds: VerifiedRadioThresholds, + reward_period: &Range>, + ) -> anyhow::Result { + let mut coverage_hash_map: HashMap> = HashMap::new(); + let mut trust_scores: HashMap< + RadioId, + Vec, + > = HashMap::new(); + let mut coverage_obj_uuid: HashMap = HashMap::new(); + let mut seniority_timestamps: HashMap> = HashMap::new(); + let mut radio_types: HashMap = + HashMap::new(); + + let mut heartbeats = std::pin::pin!(heartbeats); + while let Some(heartbeat) = heartbeats.next().await.transpose()? { + let pubkey = heartbeat.hotspot_key.clone(); + let heartbeat_key = heartbeat.key().clone(); + let cbsd_id = heartbeat_key.to_owned().into_cbsd_id(); + let key = (pubkey.clone(), cbsd_id.clone()); + + let seniority = hex_streams + .fetch_seniority(heartbeat_key, reward_period.end) + .await?; + seniority_timestamps + .entry(key.clone()) + .or_default() + .push(seniority.clone()); + + trust_scores + .entry(key.clone()) + .or_default() + .extend(make_heartbeat_trust_scores(&heartbeat)); + + coverage_obj_uuid.insert(key.clone(), heartbeat.coverage_object); + + let covered_hexes_stream = hex_streams + .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) + .await?; + + // FIXME: Is there a way to make is_indoor look nicer? + let mut is_indoor = false; + let mut covered_hexes = std::pin::pin!(covered_hexes_stream); + while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { + is_indoor = hex_coverage.indoor; + coverage_hash_map + .entry(key.clone()) + .or_default() + .push(hex_coverage); + } + + let radio_type = match (is_indoor, cbsd_id.is_some()) { + (true, false) => coverage_point_calculator::RadioType::IndoorWifi, + (true, true) => coverage_point_calculator::RadioType::IndoorCbrs, + (false, false) => coverage_point_calculator::RadioType::OutdoorWifi, + (false, true) => coverage_point_calculator::RadioType::OutdoorCbrs, + }; + radio_types.insert(key.clone(), radio_type); + } + + let mut covered_hexes = CoveredHexes::default(); + + let coverage_map = { + let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); + for (key, coverage) in &coverage_hash_map { + covered_hexes + .aggregate_coverage( + &key.0, + &boosted_hexes, + stream::iter(coverage.iter().cloned().map(anyhow::Ok)), + ) + .await?; + + let seniority = { + let mut seniorities = seniority_timestamps + .get(&key) + .expect("seniority gauranteed") + .clone(); + seniorities.sort_by_key(|s| s.seniority_ts); + let first = seniorities.first().cloned(); + first.expect("seniority gauranteed") + }; + + let is_indoor = coverage.first().unwrap().indoor; + let (pubkey, cbsd_id) = &key; + + let coverage = coverage + .into_iter() + .cloned() + .map(|hex_coverage| coverage_map::UnrankedCoverage { + location: hex_coverage.hex, + signal_power: hex_coverage.signal_power, + signal_level: hex_coverage.signal_level.into(), + assignments: hex_coverage.assignments, + }) + .collect(); + + let coverage_obj = coverage_map::CoverageObject { + indoor: is_indoor, + hotspot_key: pubkey.clone(), + cbsd_id: cbsd_id.clone(), + seniority_timestamp: seniority.seniority_ts, + coverage, + }; + coverage_map_builder.insert_coverage_object(coverage_obj); + } + coverage_map_builder.build(&boosted_hexes, reward_period.start) + }; + + println!("COVERAGE MAP: "); + println!("{coverage_map:?}"); + println!(""); + println!(""); + + Ok(Self { + coverage_map: InnerCoverageMap { + coverage_hash_map, + covered_hexes, + boosted_hexes, + reward_period: reward_period.clone(), + coverage_map, + }, + verified_radio_thresholds, + speedtest_averages, + trust_scores, + seniority_timestamps, + radio_types, + coverage_obj_uuid, + }) + } + + pub async fn all_radio_ids(&mut self) -> anyhow::Result> { + Ok(self.radio_types.keys().cloned().collect()) + } + + pub fn get_seniority(&self, key: &RadioId) -> Option { + let mut s = self + .seniority_timestamps + .get(key) + .expect("seniority timestamps") + .clone(); + s.sort_by_key(|f| f.seniority_ts); + s.first().cloned() + } + + fn radio_type(&self, key: &RadioId) -> coverage_point_calculator::RadioType { + self.radio_types.get(key).cloned().expect("radio type") + } fn radio_threshold_verified(&self, key: &RadioId) -> coverage_point_calculator::RadioThreshold { match self @@ -578,11 +647,8 @@ impl CoveragePoints2 { } } - pub fn get_coverage_object_uuid(&self, key: &RadioId) -> Option { - self.radio_heartbeats - .get(key) - .and_then(|v| v.first()) - .map(|v| v.coverage_object) + fn get_coverage_object_uuid(&self, key: &RadioId) -> Option { + self.coverage_obj_uuid.get(key).cloned() } pub async fn coverage_points( @@ -601,47 +667,46 @@ impl CoveragePoints2 { }); } - let mut trust_scores = vec![]; - for heartbeat in self.radio_heartbeats.get(&radio_id).unwrap_or(&vec![]) { - use coverage_point_calculator::location; - - // FIXME: what do we do for radios with no asserted distances? - // ? unwrapping an option of distances? - let fallback: Vec = std::iter::repeat(0) - .take(heartbeat.trust_score_multipliers.len()) - .collect(); - let combos = heartbeat - .distances_to_asserted - .as_ref() - .unwrap_or(&fallback) - .iter() - .zip(heartbeat.trust_score_multipliers.iter()); - - for (&distance, &trust_score) in combos { - trust_scores.push(location::LocationTrust { - distance_to_asserted: location::Meters::new(distance as u32), - trust_score, - }) - } - } - - let radio = RewardableRadio::new( - self.radio_type(&radio_id), - speedtests, - trust_scores, - self.radio_threshold_verified(&radio_id), - self.hexes(&radio_id), - ); + let radio_type = self.radio_type(&radio_id); + let trust_scores = self + .trust_scores + .get(&radio_id) + .expect("trust scores") + .clone(); + let verified = self.radio_threshold_verified(&radio_id); + let hexes = self.coverage_map.hexes(&radio_id, &radio_type); + let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes); let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); Ok(coverage_points) } } -impl coverage_point_calculator::CoverageMap for CoveragePoints2 { - fn hexes(&self, radio_id: &RadioId) -> Vec { - self.coverage_map +fn make_heartbeat_trust_scores(heartbeat: &HeartbeatReward) -> Vec { + let fallback: Vec = std::iter::repeat(0) + .take(heartbeat.trust_score_multipliers.len()) + .collect(); + heartbeat + .distances_to_asserted + .as_ref() + .unwrap_or(&fallback) + .iter() + .zip(heartbeat.trust_score_multipliers.iter()) + .map(|(&distance, &trust_score)| location::LocationTrust { + distance_to_asserted: location::Meters::new(distance as u32), + trust_score, + }) + .collect() +} + +impl coverage_point_calculator::CoverageMapExt for InnerCoverageMap { + fn hexes( + &self, + radio_id: &RadioId, + radio_type: &coverage_point_calculator::RadioType, + ) -> Vec { + self.coverage_hash_map .get(radio_id) .expect("coverage map") .into_iter() @@ -649,16 +714,36 @@ impl coverage_point_calculator::CoverageMap for CoveragePoints2 { let boosted_hex = self .boosted_hexes .get_current_multiplier(hex.hex, self.reward_period.start); + coverage_point_calculator::CoveredHex { cell: hex.hex, - rank: self.radio_rank(&radio_id, &hex), + rank: self.radio_rank(radio_id, radio_type, hex), signal_level: hex.signal_level.into(), assignments: hex.assignments.clone(), boosted: boosted_hex, } }) - .collect::>() + .collect() } + + // fn hexes_matty(&self, radio_id: &RadioId) -> Vec { + // let (pubkey, cbsd_id) = radio_id; + // let ranked_coverage = match cbsd_id { + // Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), + // None => self.coverage_map.get_wifi_coverage(pubkey), + // }; + // ranked_coverage + // .to_vec() + // .into_iter() + // .map(|ranked| coverage_point_calculator::CoveredHex { + // cell: ranked.hex, + // rank: NonZeroUsize::new(ranked.rank).expect("ranked coverage rank >1"), + // signal_level: ranked.signal_level, + // assignments: ranked.assignments, + // boosted: ranked.boosted, + // }) + // .collect() + // } } pub fn coverage_point_to_mobile_reward_share( @@ -749,15 +834,6 @@ impl CoveragePoints { .coverage_points } - pub fn total_shares(&self) -> Decimal { - todo!() - // self.coverage_points - // .values() - // .fold(Decimal::ZERO, |sum, radio_points| { - // sum + radio_points.total_points() - // }) - } - pub async fn into_rewards( mut self, available_poc_rewards: Decimal, @@ -771,13 +847,14 @@ impl CoveragePoints { let mut total_shares: Decimal = dec!(0); - let mut stuff = vec![]; + let mut processed_radios = vec![]; for id in radio_ids { let points = self .coverage_points .coverage_points(&id) .await .expect("coverage points"); + let seniority = self .coverage_points .get_seniority(&id) @@ -788,7 +865,7 @@ impl CoveragePoints { .expect("coverage_object_uuid"); total_shares += points.coverage_points; - stuff.push((id, points, seniority, coverage_object_uuid)); + processed_radios.push((id, points, seniority, coverage_object_uuid)); } let Some(rewards_per_share) = available_poc_rewards.checked_div(total_shares) else { @@ -797,7 +874,7 @@ impl CoveragePoints { }; Some( - stuff + processed_radios .into_iter() .map(move |(id, points, seniority, coverage_object_uuid)| { let poc_reward = rewards_per_share * points.coverage_points; @@ -2149,65 +2226,6 @@ mod test { let c2 = "P27-SCE4255W2107CW5000015".to_string(); let c3 = "2AG32PBS3101S1202000464223GY0153".to_string(); - // let mut coverage_points = HashMap::new(); - - // coverage_points.insert( - // gw1.clone(), - // HotspotPoints { - // speedtest_multiplier: dec!(1.0), - // radio_points: vec![( - // Some(c1), - // RadioPoints { - // location_trust_score_multiplier: dec!(1.0), - // seniority: DateTime::default(), - // coverage_object: Uuid::new_v4(), - // reward_points: vec![CoverageRewardPointsWithMultiplier { - // coverage_points: CoverageRewardPoints { - // boost_multiplier: NonZeroU32::new(1).unwrap(), - // coverage_points: dec!(10.0), - // hex_assignments: hex_assignments_mock(), - // rank: None, - // }, - // boosted_hex: BoostedHex { - // location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - // multiplier: NonZeroU32::new(1).unwrap(), - // }, - // }], - // }, - // )] - // .into_iter() - // .collect(), - // }, - // ); - // coverage_points.insert( - // gw2.clone(), - // HotspotPoints { - // speedtest_multiplier: dec!(1.0), - // radio_points: vec![ - // ( - // Some(c2), - // RadioPoints { - // location_trust_score_multiplier: dec!(1.0), - // seniority: DateTime::default(), - // coverage_object: Uuid::new_v4(), - // reward_points: vec![], - // }, - // ), - // ( - // Some(c3), - // RadioPoints { - // location_trust_score_multiplier: dec!(1.0), - // reward_points: vec![], - // seniority: DateTime::default(), - // coverage_object: Uuid::new_v4(), - // }, - // ), - // ] - // .into_iter() - // .collect(), - // }, - // ); - let now = Utc::now(); let epoch = now - Duration::hours(1)..now; // We should never see any radio shares from owner2, since all of them are @@ -2258,13 +2276,26 @@ mod test { let uuid_1 = Uuid::new_v4(); let uuid_2 = Uuid::new_v4(); - let mut radio_heartbeats = HashMap::new(); + let mut radio_types: HashMap< + (PublicKeyBinary, Option), + coverage_point_calculator::RadioType, + > = HashMap::new(); + radio_types.insert( + (gw1.clone(), None), + coverage_point_calculator::RadioType::IndoorWifi, + ); + radio_types.insert( + (gw2.clone(), None), + coverage_point_calculator::RadioType::IndoorWifi, + ); + + let mut radio_heartbeats = HashMap::>::new(); radio_heartbeats.insert( (gw1.clone(), None), vec![HeartbeatReward { hotspot_key: gw1.clone(), cbsd_id: None, - cell_type: CellType::Nova430I, + cell_type: CellType::NovaGenericWifiIndoor, distances_to_asserted: None, trust_score_multipliers: vec![dec!(1)], coverage_object: uuid_1, @@ -2275,20 +2306,41 @@ mod test { vec![HeartbeatReward { hotspot_key: gw2.clone(), cbsd_id: None, - cell_type: CellType::Nova430I, + cell_type: CellType::NovaGenericWifiIndoor, distances_to_asserted: None, trust_score_multipliers: vec![dec!(1)], coverage_object: uuid_2, }], ); - let mut coverage_map = HashMap::new(); - coverage_map.insert( + + let mut coverage_obj_uuid = HashMap::new(); + coverage_obj_uuid.insert((gw1.clone(), None), uuid_1); + coverage_obj_uuid.insert((gw2.clone(), None), uuid_2); + + let mut trust_scores = HashMap::new(); + trust_scores.insert( + (gw1.clone(), None), + vec![location::LocationTrust { + distance_to_asserted: location::Meters::new(0), + trust_score: dec!(1), + }], + ); + trust_scores.insert( + (gw2.clone(), None), + vec![location::LocationTrust { + distance_to_asserted: location::Meters::new(0), + trust_score: dec!(1), + }], + ); + + let mut coverage_hash_map = HashMap::new(); + coverage_hash_map.insert( (gw1.clone(), None), vec![HexCoverage { uuid: uuid_1, - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: false, - radio_key: OwnedKeyType::Cbrs("key-1".to_string()), + hex: Cell::from_raw(0x8c2681a3064dbff).expect("valid h3 cell"), + indoor: true, + radio_key: OwnedKeyType::Wifi(gw1.clone()), signal_level: crate::coverage::SignalLevel::High, signal_power: 42, coverage_claim_time: now.clone(), @@ -2296,13 +2348,13 @@ mod test { assignments: hex_assignments_mock(), }], ); - coverage_map.insert( + coverage_hash_map.insert( (gw2.clone(), None), vec![HexCoverage { uuid: uuid_2, - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: false, - radio_key: OwnedKeyType::Cbrs("key-2".to_string()), + hex: Cell::from_raw(0x8c2681a3064ddff).expect("valid h3 cell"), + indoor: true, + radio_key: OwnedKeyType::Wifi(gw2.clone()), signal_level: crate::coverage::SignalLevel::High, signal_power: 42, coverage_claim_time: now.clone(), @@ -2310,6 +2362,46 @@ mod test { assignments: hex_assignments_mock(), }], ); + + let mut covered_hexes = CoveredHexes::default(); + for (key, coverage) in &coverage_hash_map { + covered_hexes + .aggregate_coverage( + &key.0, + &BoostedHexes::default(), + stream::iter(coverage.iter().cloned().map(|x| anyhow::Ok(x))), + ) + .await + .expect("aded covered hexes"); + } + + let mut coverage_map = coverage_map::CoverageMapBuilder::default(); + coverage_map.insert_coverage_object(coverage_map::CoverageObject { + indoor: true, + hotspot_key: gw1.clone(), + cbsd_id: None, + seniority_timestamp: now.clone(), + coverage: vec![coverage_map::UnrankedCoverage { + location: Cell::from_raw(0x8c2681a3064dbff).expect("valid h3 cell"), + signal_power: 42, + signal_level: coverage_map::SignalLevel::High, + assignments: hex_assignments_mock(), + }], + }); + coverage_map.insert_coverage_object(coverage_map::CoverageObject { + indoor: true, + hotspot_key: gw2.clone(), + cbsd_id: None, + seniority_timestamp: now.clone(), + coverage: vec![coverage_map::UnrankedCoverage { + location: Cell::from_raw(0x8c2681a3064ddff).expect("valid h3 cell"), + signal_power: 42, + signal_level: coverage_map::SignalLevel::High, + assignments: hex_assignments_mock(), + }], + }); + let coverage_map = coverage_map.build(&BoostedHexes::default(), epoch.start); + let mut seniority_timestamps = HashMap::new(); seniority_timestamps.insert( (gw1.clone(), None), @@ -2334,14 +2426,19 @@ mod test { let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { - covered_hexes: CoveredHexes::default(), - boosted_hexes: BoostedHexes::default(), + coverage_map: InnerCoverageMap { + coverage_hash_map, + covered_hexes, + boosted_hexes: BoostedHexes::default(), + reward_period: epoch.clone(), + coverage_map, + }, verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages, - radio_heartbeats, - coverage_map, - reward_period: epoch.clone(), seniority_timestamps, + trust_scores, + radio_types, + coverage_obj_uuid, }, }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); @@ -2367,14 +2464,21 @@ mod test { let epoch = now - Duration::hours(1)..now; let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { - covered_hexes: CoveredHexes::default(), - boosted_hexes: BoostedHexes::default(), + coverage_map: InnerCoverageMap { + coverage_hash_map: HashMap::new(), + covered_hexes: CoveredHexes::default(), + boosted_hexes: BoostedHexes::default(), + reward_period: epoch.clone(), + // + coverage_map: coverage_map::CoverageMapBuilder::default() + .build(&BoostedHexes::default(), epoch.start), + }, verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages: SpeedtestAverages::default(), - radio_heartbeats: HashMap::new(), - coverage_map: HashMap::new(), - reward_period: epoch.clone(), seniority_timestamps: HashMap::new(), + trust_scores: HashMap::new(), + radio_types: HashMap::new(), + coverage_obj_uuid: HashMap::new(), }, }; From e18db32bece3938cbf1124c72ac5f459834737bd Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Tue, 4 Jun 2024 11:59:10 -0700 Subject: [PATCH 12/40] Remove coverage map trait --- mobile_verifier/src/reward_shares.rs | 94 ++++++++++++++-------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 2d49c9f0f..89a9799c1 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -7,7 +7,7 @@ use crate::{ subscriber_location::SubscriberValidatedLocations, }; use chrono::{DateTime, Duration, Utc}; -use coverage_point_calculator::{location, CoverageMapExt, RewardableRadio}; +use coverage_point_calculator::{location, RewardableRadio}; use file_store::traits::TimestampEncode; use futures::{stream, Stream, StreamExt}; use helium_crypto::PublicKeyBinary; @@ -469,6 +469,50 @@ impl InnerCoverageMap { } return NonZeroUsize::MAX; } + + fn old_hexes( + &self, + radio_id: &RadioId, + radio_type: &coverage_point_calculator::RadioType, + ) -> Vec { + self.coverage_hash_map + .get(radio_id) + .expect("coverage map") + .into_iter() + .map(|hex| { + let boosted_hex = self + .boosted_hexes + .get_current_multiplier(hex.hex, self.reward_period.start); + + coverage_point_calculator::CoveredHex { + cell: hex.hex, + rank: self.radio_rank(radio_id, radio_type, hex), + signal_level: hex.signal_level.into(), + assignments: hex.assignments.clone(), + boosted: boosted_hex, + } + }) + .collect() + } + + // fn new_hexes(&self, radio_id: &RadioId) -> Vec { + // let (pubkey, cbsd_id) = radio_id; + // let ranked_coverage = match cbsd_id { + // Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), + // None => self.coverage_map.get_wifi_coverage(pubkey), + // }; + // ranked_coverage + // .to_vec() + // .into_iter() + // .map(|ranked| coverage_point_calculator::CoveredHex { + // cell: ranked.hex, + // rank: NonZeroUsize::new(ranked.rank).expect("ranked coverage rank >1"), + // signal_level: ranked.signal_level, + // assignments: ranked.assignments, + // boosted: ranked.boosted, + // }) + // .collect() + // } } #[derive(Debug)] @@ -674,7 +718,7 @@ impl CoveragePoints2 { .expect("trust scores") .clone(); let verified = self.radio_threshold_verified(&radio_id); - let hexes = self.coverage_map.hexes(&radio_id, &radio_type); + let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes); let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); @@ -700,52 +744,6 @@ fn make_heartbeat_trust_scores(heartbeat: &HeartbeatReward) -> Vec for InnerCoverageMap { - fn hexes( - &self, - radio_id: &RadioId, - radio_type: &coverage_point_calculator::RadioType, - ) -> Vec { - self.coverage_hash_map - .get(radio_id) - .expect("coverage map") - .into_iter() - .map(|hex| { - let boosted_hex = self - .boosted_hexes - .get_current_multiplier(hex.hex, self.reward_period.start); - - coverage_point_calculator::CoveredHex { - cell: hex.hex, - rank: self.radio_rank(radio_id, radio_type, hex), - signal_level: hex.signal_level.into(), - assignments: hex.assignments.clone(), - boosted: boosted_hex, - } - }) - .collect() - } - - // fn hexes_matty(&self, radio_id: &RadioId) -> Vec { - // let (pubkey, cbsd_id) = radio_id; - // let ranked_coverage = match cbsd_id { - // Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), - // None => self.coverage_map.get_wifi_coverage(pubkey), - // }; - // ranked_coverage - // .to_vec() - // .into_iter() - // .map(|ranked| coverage_point_calculator::CoveredHex { - // cell: ranked.hex, - // rank: NonZeroUsize::new(ranked.rank).expect("ranked coverage rank >1"), - // signal_level: ranked.signal_level, - // assignments: ranked.assignments, - // boosted: ranked.boosted, - // }) - // .collect() - // } -} - pub fn coverage_point_to_mobile_reward_share( coverage_points: coverage_point_calculator::CoveragePoints, start_period: u64, From 6ad904476fc2fe814efb50c59c0e952310791091 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Tue, 4 Jun 2024 14:53:47 -0700 Subject: [PATCH 13/40] use RankedCoverage from coverage-map --- mobile_verifier/src/reward_shares.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 89a9799c1..40bdeb3f7 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -474,7 +474,7 @@ impl InnerCoverageMap { &self, radio_id: &RadioId, radio_type: &coverage_point_calculator::RadioType, - ) -> Vec { + ) -> Vec { self.coverage_hash_map .get(radio_id) .expect("coverage map") @@ -484,9 +484,11 @@ impl InnerCoverageMap { .boosted_hexes .get_current_multiplier(hex.hex, self.reward_period.start); - coverage_point_calculator::CoveredHex { - cell: hex.hex, - rank: self.radio_rank(radio_id, radio_type, hex), + coverage_map::RankedCoverage { + hotspot_key: radio_id.0.clone(), + cbsd_id: radio_id.1.clone(), + hex: hex.hex, + rank: self.radio_rank(radio_id, radio_type, hex).get(), signal_level: hex.signal_level.into(), assignments: hex.assignments.clone(), boosted: boosted_hex, @@ -761,9 +763,9 @@ pub fn coverage_point_to_mobile_reward_share( .iter_covered_hexes() .filter(move |_| radio_verified) .filter(move |_| eligible_for_boosted) - .filter(|covered_hex| covered_hex.is_boosted()) + .filter(|covered_hex| covered_hex.boosted.is_some()) .map(|covered_hex| proto::BoostedHex { - location: covered_hex.cell.into_raw(), + location: covered_hex.hex.into_raw(), multiplier: covered_hex.boosted.unwrap().into(), }) .collect(); From 2fb1984d319c409e39f9faf63fe12ac9bd69bd60 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Tue, 4 Jun 2024 15:34:12 -0700 Subject: [PATCH 14/40] Skip a radio for rewards if it cannot be constructed properly rewards should never fail. but we can skip a radio and have an alert that says something went wrong. --- mobile_verifier/src/reward_shares.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 40bdeb3f7..46b7a3c14 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -722,7 +722,7 @@ impl CoveragePoints2 { let verified = self.radio_threshold_verified(&radio_id); let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); - let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes); + let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes)?; let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); Ok(coverage_points) @@ -849,11 +849,14 @@ impl CoveragePoints { let mut processed_radios = vec![]; for id in radio_ids { - let points = self - .coverage_points - .coverage_points(&id) - .await - .expect("coverage points"); + let points_res = self.coverage_points.coverage_points(&id).await; + let points = match points_res { + Ok(points) => points, + Err(err) => { + tracing::error!(pubkey = id.0.to_string(), ?err, "could not reward radio"); + continue; + } + }; let seniority = self .coverage_points From fca62dd96bde610f69137ecfde2deb04fcb75233 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 5 Jun 2024 14:19:27 -0700 Subject: [PATCH 15/40] use new coverage_map for testing --- mobile_verifier/src/reward_shares.rs | 67 ++++++++++++---------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 46b7a3c14..1da79d4fc 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -497,24 +497,14 @@ impl InnerCoverageMap { .collect() } - // fn new_hexes(&self, radio_id: &RadioId) -> Vec { - // let (pubkey, cbsd_id) = radio_id; - // let ranked_coverage = match cbsd_id { - // Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), - // None => self.coverage_map.get_wifi_coverage(pubkey), - // }; - // ranked_coverage - // .to_vec() - // .into_iter() - // .map(|ranked| coverage_point_calculator::CoveredHex { - // cell: ranked.hex, - // rank: NonZeroUsize::new(ranked.rank).expect("ranked coverage rank >1"), - // signal_level: ranked.signal_level, - // assignments: ranked.assignments, - // boosted: ranked.boosted, - // }) - // .collect() - // } + fn new_hexes(&self, radio_id: &RadioId) -> Vec { + let (pubkey, cbsd_id) = radio_id; + let ranked_coverage = match cbsd_id { + Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), + None => self.coverage_map.get_wifi_coverage(pubkey), + }; + ranked_coverage.to_vec().into_iter().collect() + } } #[derive(Debug)] @@ -710,6 +700,7 @@ impl CoveragePoints2 { upload_speed: speedtest::BytesPs::new(test.report.upload_speed), download_speed: speedtest::BytesPs::new(test.report.download_speed), latency: speedtest::Millis::new(test.report.latency), + timestamp: test.report.timestamp, }); } @@ -720,7 +711,8 @@ impl CoveragePoints2 { .expect("trust scores") .clone(); let verified = self.radio_threshold_verified(&radio_id); - let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); + // let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); + let hexes = self.coverage_map.new_hexes(&radio_id); let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes)?; let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); @@ -756,28 +748,25 @@ pub fn coverage_point_to_mobile_reward_share( seniority_timestamp: DateTime, coverage_object_uuid: Uuid, ) -> proto::MobileRewardShare { - let radio_verified = coverage_points.radio.radio_threshold_met(); - let eligible_for_boosted = coverage_points.radio.eligible_for_boosted_hexes(); let boosted_hexes = coverage_points - .radio - .iter_covered_hexes() - .filter(move |_| radio_verified) - .filter(move |_| eligible_for_boosted) - .filter(|covered_hex| covered_hex.boosted.is_some()) + .iter_boosted_hexes() .map(|covered_hex| proto::BoostedHex { location: covered_hex.hex.into_raw(), - multiplier: covered_hex.boosted.unwrap().into(), + multiplier: covered_hex.boosted_multiplier.unwrap().to_u32().unwrap(), }) .collect(); - let location_multiplier = coverage_points.radio.location_trust_multiplier(); - let speedtest_multiplier = coverage_points.radio.speedtest_multiplier(); - let to_proto_value = |value: Decimal| (value * dec!(1000)).to_u32().unwrap_or_default(); + let location_trust_score_multiplier = to_proto_value(coverage_points.location_trust_multiplier); + let speedtest_multiplier = to_proto_value(coverage_points.speedtest_multiplier); + let coverage_points = coverage_points + .hex_coverage_points + .to_u64() + .unwrap_or_default(); - // Does not include location and speedtest multipliers - let hex_coverage_points = coverage_points.radio.hex_coverage_points(); + let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); + // Does not include location and speedtest multipliers proto::MobileRewardShare { start_period, end_period, @@ -786,11 +775,11 @@ pub fn coverage_point_to_mobile_reward_share( hotspot_key: hotspot_key.clone().into(), cbsd_id: cbsd_id.unwrap_or_default(), poc_reward, - coverage_points: hex_coverage_points.to_u64().unwrap_or(0), + coverage_points, seniority_timestamp: seniority_timestamp.encode_timestamp(), - coverage_object: Vec::from(coverage_object_uuid.into_bytes()), - location_trust_score_multiplier: to_proto_value(location_multiplier), - speedtest_multiplier: to_proto_value(speedtest_multiplier), + coverage_object, + location_trust_score_multiplier, + speedtest_multiplier, boosted_hexes, ..Default::default() }, @@ -831,7 +820,7 @@ impl CoveragePoints { .coverage_points(hotspot) .await .expect("coverage points for hotspot") - .coverage_points + .total_coverage_points } pub async fn into_rewards( @@ -867,7 +856,7 @@ impl CoveragePoints { .get_coverage_object_uuid(&id) .expect("coverage_object_uuid"); - total_shares += points.coverage_points; + total_shares += points.total_coverage_points; processed_radios.push((id, points, seniority, coverage_object_uuid)); } @@ -880,7 +869,7 @@ impl CoveragePoints { processed_radios .into_iter() .map(move |(id, points, seniority, coverage_object_uuid)| { - let poc_reward = rewards_per_share * points.coverage_points; + let poc_reward = rewards_per_share * points.total_coverage_points; let poc_reward = poc_reward .round_dp_with_strategy(0, RoundingStrategy::ToZero) .to_u64() From fec7b71254f691c32dbb0dad91f7f76cc041bf5f Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 7 Jun 2024 17:16:45 -0700 Subject: [PATCH 16/40] bring up to date with coverage-point-calculator --- mobile_verifier/src/reward_shares.rs | 67 +++++++++++++++------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 1da79d4fc..85c90bd26 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -7,7 +7,6 @@ use crate::{ subscriber_location::SubscriberValidatedLocations, }; use chrono::{DateTime, Duration, Utc}; -use coverage_point_calculator::{location, RewardableRadio}; use file_store::traits::TimestampEncode; use futures::{stream, Stream, StreamExt}; use helium_crypto::PublicKeyBinary; @@ -485,7 +484,7 @@ impl InnerCoverageMap { .get_current_multiplier(hex.hex, self.reward_period.start); coverage_map::RankedCoverage { - hotspot_key: radio_id.0.clone(), + hotspot_key: radio_id.0.clone().into(), cbsd_id: radio_id.1.clone(), hex: hex.hex, rank: self.radio_rank(radio_id, radio_type, hex).get(), @@ -501,7 +500,7 @@ impl InnerCoverageMap { let (pubkey, cbsd_id) = radio_id; let ranked_coverage = match cbsd_id { Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), - None => self.coverage_map.get_wifi_coverage(pubkey), + None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), }; ranked_coverage.to_vec().into_iter().collect() } @@ -513,7 +512,7 @@ pub struct CoveragePoints2 { // verified_radio_thresholds: VerifiedRadioThresholds, speedtest_averages: SpeedtestAverages, - trust_scores: HashMap>, + trust_scores: HashMap>, seniority_timestamps: HashMap>, radio_types: HashMap, coverage_obj_uuid: HashMap, @@ -529,10 +528,8 @@ impl CoveragePoints2 { reward_period: &Range>, ) -> anyhow::Result { let mut coverage_hash_map: HashMap> = HashMap::new(); - let mut trust_scores: HashMap< - RadioId, - Vec, - > = HashMap::new(); + let mut trust_scores: HashMap> = + HashMap::new(); let mut coverage_obj_uuid: HashMap = HashMap::new(); let mut seniority_timestamps: HashMap> = HashMap::new(); let mut radio_types: HashMap = @@ -623,7 +620,7 @@ impl CoveragePoints2 { let coverage_obj = coverage_map::CoverageObject { indoor: is_indoor, - hotspot_key: pubkey.clone(), + hotspot_key: pubkey.clone().into(), cbsd_id: cbsd_id.clone(), seniority_timestamp: seniority.seniority_ts, coverage, @@ -679,7 +676,7 @@ impl CoveragePoints2 { .is_verified(key.0.clone(), key.1.clone()) { true => coverage_point_calculator::RadioThreshold::Verified, - false => coverage_point_calculator::RadioThreshold::UnVerified, + false => coverage_point_calculator::RadioThreshold::Unverified, } } @@ -695,11 +692,11 @@ impl CoveragePoints2 { let pubkey = &radio_id.0; let average = self.speedtest_averages.get_average(pubkey).unwrap(); for test in average.speedtests { - use coverage_point_calculator::speedtest; - speedtests.push(speedtest::Speedtest { - upload_speed: speedtest::BytesPs::new(test.report.upload_speed), - download_speed: speedtest::BytesPs::new(test.report.download_speed), - latency: speedtest::Millis::new(test.report.latency), + use coverage_point_calculator::{BytesPs, Speedtest}; + speedtests.push(Speedtest { + upload_speed: BytesPs::new(test.report.upload_speed), + download_speed: BytesPs::new(test.report.download_speed), + latency_millis: test.report.latency, timestamp: test.report.timestamp, }); } @@ -714,14 +711,21 @@ impl CoveragePoints2 { // let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); let hexes = self.coverage_map.new_hexes(&radio_id); - let radio = RewardableRadio::new(radio_type, speedtests, trust_scores, verified, hexes)?; - let coverage_points = coverage_point_calculator::calculate_coverage_points(radio); + let coverage_points = coverage_point_calculator::CoveragePoints::new( + radio_type, + verified, + speedtests, + trust_scores, + hexes, + )?; Ok(coverage_points) } } -fn make_heartbeat_trust_scores(heartbeat: &HeartbeatReward) -> Vec { +fn make_heartbeat_trust_scores( + heartbeat: &HeartbeatReward, +) -> Vec { let fallback: Vec = std::iter::repeat(0) .take(heartbeat.trust_score_multipliers.len()) .collect(); @@ -731,10 +735,12 @@ fn make_heartbeat_trust_scores(heartbeat: &HeartbeatReward) -> Vec proto::MobileRewardShare { let boosted_hexes = coverage_points - .iter_boosted_hexes() + .covered_hexes + .iter() + .filter(|hex| hex.boosted_multiplier.is_some()) .map(|covered_hex| proto::BoostedHex { location: covered_hex.hex.into_raw(), multiplier: covered_hex.boosted_multiplier.unwrap().to_u32().unwrap(), @@ -766,7 +774,6 @@ pub fn coverage_point_to_mobile_reward_share( let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); - // Does not include location and speedtest multipliers proto::MobileRewardShare { start_period, end_period, @@ -2312,15 +2319,15 @@ mod test { let mut trust_scores = HashMap::new(); trust_scores.insert( (gw1.clone(), None), - vec![location::LocationTrust { - distance_to_asserted: location::Meters::new(0), + vec![coverage_point_calculator::LocationTrust { + meters_to_asserted: 0, trust_score: dec!(1), }], ); trust_scores.insert( (gw2.clone(), None), - vec![location::LocationTrust { - distance_to_asserted: location::Meters::new(0), + vec![coverage_point_calculator::LocationTrust { + meters_to_asserted: 0, trust_score: dec!(1), }], ); @@ -2370,7 +2377,7 @@ mod test { let mut coverage_map = coverage_map::CoverageMapBuilder::default(); coverage_map.insert_coverage_object(coverage_map::CoverageObject { indoor: true, - hotspot_key: gw1.clone(), + hotspot_key: gw1.clone().into(), cbsd_id: None, seniority_timestamp: now.clone(), coverage: vec![coverage_map::UnrankedCoverage { @@ -2382,7 +2389,7 @@ mod test { }); coverage_map.insert_coverage_object(coverage_map::CoverageObject { indoor: true, - hotspot_key: gw2.clone(), + hotspot_key: gw2.clone().into(), cbsd_id: None, seniority_timestamp: now.clone(), coverage: vec![coverage_map::UnrankedCoverage { From ab773382cae66500d0d635fad4ca00bb7c927963 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 7 Jun 2024 17:22:24 -0700 Subject: [PATCH 17/40] remove unused fields now that coverage-map is stabilized --- mobile_verifier/src/reward_shares.rs | 177 +-------------------------- 1 file changed, 3 insertions(+), 174 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 85c90bd26..99c645d89 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -25,7 +25,7 @@ use mobile_config::{ }; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -use std::{collections::HashMap, num::NonZeroUsize, ops::Range}; +use std::{collections::HashMap, ops::Range}; use uuid::Uuid; /// Total tokens emissions pool per 365 days or 366 days for a leap year @@ -380,122 +380,10 @@ type RadioId = (PublicKeyBinary, Option); #[derive(Debug)] struct InnerCoverageMap { - coverage_hash_map: HashMap>, - covered_hexes: CoveredHexes, - boosted_hexes: BoostedHexes, - reward_period: Range>, - // in the end, we should be able to swap out the above members with the coverage_map crate. coverage_map: coverage_map::CoverageMap, } impl InnerCoverageMap { - fn radio_rank( - &self, - key: &RadioId, - radio_type: &coverage_point_calculator::RadioType, - hex: &HexCoverage, - ) -> NonZeroUsize { - match radio_type { - coverage_point_calculator::RadioType::IndoorWifi => { - let hex_coverage = self - .covered_hexes - .indoor_wifi - .get(&hex.hex) - .expect("any indoor wifi radios covering a cell"); - let coverage_by_level = - hex_coverage - .clone() - .into_iter() - .flat_map(|(sig_level, coverage)| { - coverage - .into_sorted_vec() - .into_iter() - .map(move |cl| (sig_level, cl)) - }); - for (index, (_sig_level, coverage)) in coverage_by_level.enumerate() { - if coverage.hotspot == key.0 { - return NonZeroUsize::new(index + 1).unwrap(); - } - } - } - coverage_point_calculator::RadioType::OutdoorWifi => { - let coverage = self - .covered_hexes - .outdoor_wifi - .get(&hex.hex) - .expect("any outdoor wifi radios covering a cell"); - - for (index, cl) in coverage.clone().into_sorted_vec().iter().enumerate() { - if cl.hotspot == key.0 { - return NonZeroUsize::new(index + 1).unwrap(); - } - } - } - coverage_point_calculator::RadioType::IndoorCbrs => { - let hex_coverage = self - .covered_hexes - .indoor_cbrs - .get(&hex.hex) - .expect("any indoor cbrs radios covering a cell"); - let coverage_by_level = - hex_coverage - .clone() - .into_iter() - .flat_map(|(sig_level, coverage)| { - coverage - .into_sorted_vec() - .into_iter() - .map(move |cl| (sig_level, cl)) - }); - for (index, (_signal_level, coverage)) in coverage_by_level.enumerate() { - if coverage.hotspot == key.0 { - return NonZeroUsize::new(index + 1).unwrap(); - } - } - } - coverage_point_calculator::RadioType::OutdoorCbrs => { - let coverage = self - .covered_hexes - .outdoor_cbrs - .get(&hex.hex) - .expect("any outdoor cbrs radios covering cell"); - for (index, cl) in coverage.clone().into_sorted_vec().iter().enumerate() { - if cl.hotspot == key.0 { - return NonZeroUsize::new(index + 1).unwrap(); - } - } - } - } - return NonZeroUsize::MAX; - } - - fn old_hexes( - &self, - radio_id: &RadioId, - radio_type: &coverage_point_calculator::RadioType, - ) -> Vec { - self.coverage_hash_map - .get(radio_id) - .expect("coverage map") - .into_iter() - .map(|hex| { - let boosted_hex = self - .boosted_hexes - .get_current_multiplier(hex.hex, self.reward_period.start); - - coverage_map::RankedCoverage { - hotspot_key: radio_id.0.clone().into(), - cbsd_id: radio_id.1.clone(), - hex: hex.hex, - rank: self.radio_rank(radio_id, radio_type, hex).get(), - signal_level: hex.signal_level.into(), - assignments: hex.assignments.clone(), - boosted: boosted_hex, - } - }) - .collect() - } - fn new_hexes(&self, radio_id: &RadioId) -> Vec { let (pubkey, cbsd_id) = radio_id; let ranked_coverage = match cbsd_id { @@ -636,13 +524,7 @@ impl CoveragePoints2 { println!(""); Ok(Self { - coverage_map: InnerCoverageMap { - coverage_hash_map, - covered_hexes, - boosted_hexes, - reward_period: reward_period.clone(), - coverage_map, - }, + coverage_map: InnerCoverageMap { coverage_map }, verified_radio_thresholds, speedtest_averages, trust_scores, @@ -2332,48 +2214,6 @@ mod test { }], ); - let mut coverage_hash_map = HashMap::new(); - coverage_hash_map.insert( - (gw1.clone(), None), - vec![HexCoverage { - uuid: uuid_1, - hex: Cell::from_raw(0x8c2681a3064dbff).expect("valid h3 cell"), - indoor: true, - radio_key: OwnedKeyType::Wifi(gw1.clone()), - signal_level: crate::coverage::SignalLevel::High, - signal_power: 42, - coverage_claim_time: now.clone(), - inserted_at: now.clone(), - assignments: hex_assignments_mock(), - }], - ); - coverage_hash_map.insert( - (gw2.clone(), None), - vec![HexCoverage { - uuid: uuid_2, - hex: Cell::from_raw(0x8c2681a3064ddff).expect("valid h3 cell"), - indoor: true, - radio_key: OwnedKeyType::Wifi(gw2.clone()), - signal_level: crate::coverage::SignalLevel::High, - signal_power: 42, - coverage_claim_time: now.clone(), - inserted_at: now.clone(), - assignments: hex_assignments_mock(), - }], - ); - - let mut covered_hexes = CoveredHexes::default(); - for (key, coverage) in &coverage_hash_map { - covered_hexes - .aggregate_coverage( - &key.0, - &BoostedHexes::default(), - stream::iter(coverage.iter().cloned().map(|x| anyhow::Ok(x))), - ) - .await - .expect("aded covered hexes"); - } - let mut coverage_map = coverage_map::CoverageMapBuilder::default(); coverage_map.insert_coverage_object(coverage_map::CoverageObject { indoor: true, @@ -2425,13 +2265,7 @@ mod test { let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { - coverage_map: InnerCoverageMap { - coverage_hash_map, - covered_hexes, - boosted_hexes: BoostedHexes::default(), - reward_period: epoch.clone(), - coverage_map, - }, + coverage_map: InnerCoverageMap { coverage_map }, verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages, seniority_timestamps, @@ -2464,11 +2298,6 @@ mod test { let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { coverage_map: InnerCoverageMap { - coverage_hash_map: HashMap::new(), - covered_hexes: CoveredHexes::default(), - boosted_hexes: BoostedHexes::default(), - reward_period: epoch.clone(), - // coverage_map: coverage_map::CoverageMapBuilder::default() .build(&BoostedHexes::default(), epoch.start), }, From 75ef0d44a31be863b2b74fb0846d89851582376f Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 7 Jun 2024 17:25:42 -0700 Subject: [PATCH 18/40] remove innner coverage map wrapper --- mobile_verifier/src/reward_shares.rs | 55 ++++++++-------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 99c645d89..3b9c3e3bb 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -376,27 +376,10 @@ pub fn dc_to_mobile_bones(dc_amount: Decimal, mobile_bone_price: Decimal) -> Dec } type RadioId = (PublicKeyBinary, Option); -// type RadioId = PublicKeyBinary; - -#[derive(Debug)] -struct InnerCoverageMap { - coverage_map: coverage_map::CoverageMap, -} - -impl InnerCoverageMap { - fn new_hexes(&self, radio_id: &RadioId) -> Vec { - let (pubkey, cbsd_id) = radio_id; - let ranked_coverage = match cbsd_id { - Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), - None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), - }; - ranked_coverage.to_vec().into_iter().collect() - } -} #[derive(Debug)] pub struct CoveragePoints2 { - coverage_map: InnerCoverageMap, + coverage_map: coverage_map::CoverageMap, // verified_radio_thresholds: VerifiedRadioThresholds, speedtest_averages: SpeedtestAverages, @@ -469,19 +452,9 @@ impl CoveragePoints2 { radio_types.insert(key.clone(), radio_type); } - let mut covered_hexes = CoveredHexes::default(); - let coverage_map = { let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); for (key, coverage) in &coverage_hash_map { - covered_hexes - .aggregate_coverage( - &key.0, - &boosted_hexes, - stream::iter(coverage.iter().cloned().map(anyhow::Ok)), - ) - .await?; - let seniority = { let mut seniorities = seniority_timestamps .get(&key) @@ -518,13 +491,8 @@ impl CoveragePoints2 { coverage_map_builder.build(&boosted_hexes, reward_period.start) }; - println!("COVERAGE MAP: "); - println!("{coverage_map:?}"); - println!(""); - println!(""); - Ok(Self { - coverage_map: InnerCoverageMap { coverage_map }, + coverage_map, verified_radio_thresholds, speedtest_averages, trust_scores, @@ -534,6 +502,15 @@ impl CoveragePoints2 { }) } + fn new_hexes(&self, radio_id: &RadioId) -> Vec { + let (pubkey, cbsd_id) = radio_id; + let ranked_coverage = match cbsd_id { + Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), + None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), + }; + ranked_coverage.to_vec().into_iter().collect() + } + pub async fn all_radio_ids(&mut self) -> anyhow::Result> { Ok(self.radio_types.keys().cloned().collect()) } @@ -591,7 +568,7 @@ impl CoveragePoints2 { .clone(); let verified = self.radio_threshold_verified(&radio_id); // let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); - let hexes = self.coverage_map.new_hexes(&radio_id); + let hexes = self.new_hexes(&radio_id); let coverage_points = coverage_point_calculator::CoveragePoints::new( radio_type, @@ -2265,7 +2242,7 @@ mod test { let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { - coverage_map: InnerCoverageMap { coverage_map }, + coverage_map, verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages, seniority_timestamps, @@ -2297,10 +2274,8 @@ mod test { let epoch = now - Duration::hours(1)..now; let coverage_points = CoveragePoints { coverage_points: CoveragePoints2 { - coverage_map: InnerCoverageMap { - coverage_map: coverage_map::CoverageMapBuilder::default() - .build(&BoostedHexes::default(), epoch.start), - }, + coverage_map: coverage_map::CoverageMapBuilder::default() + .build(&BoostedHexes::default(), epoch.start), verified_radio_thresholds: VerifiedRadioThresholds::default(), speedtest_averages: SpeedtestAverages::default(), seniority_timestamps: HashMap::new(), From 830b89bed3365d8d544efcdd1e23703426614b93 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 7 Jun 2024 17:26:27 -0700 Subject: [PATCH 19/40] rename after move from unwrapping --- mobile_verifier/src/reward_shares.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 3b9c3e3bb..a4c291fbb 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -502,7 +502,7 @@ impl CoveragePoints2 { }) } - fn new_hexes(&self, radio_id: &RadioId) -> Vec { + fn radio_hexes(&self, radio_id: &RadioId) -> Vec { let (pubkey, cbsd_id) = radio_id; let ranked_coverage = match cbsd_id { Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), @@ -567,8 +567,7 @@ impl CoveragePoints2 { .expect("trust scores") .clone(); let verified = self.radio_threshold_verified(&radio_id); - // let hexes = self.coverage_map.old_hexes(&radio_id, &radio_type); - let hexes = self.new_hexes(&radio_id); + let hexes = self.radio_hexes(&radio_id); let coverage_points = coverage_point_calculator::CoveragePoints::new( radio_type, From 2bdbc8639cc30fa8b324b798c7d876fe377fb787 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 7 Jun 2024 17:34:34 -0700 Subject: [PATCH 20/40] lift coverage points one more time --- mobile_verifier/src/reward_shares.rs | 243 ++++++++++++--------------- 1 file changed, 104 insertions(+), 139 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index a4c291fbb..13d318af0 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1,5 +1,5 @@ use crate::{ - coverage::{CoveredHexStream, CoveredHexes, HexCoverage, Seniority}, + coverage::{CoveredHexStream, HexCoverage, Seniority}, data_session::{HotspotMap, ServiceProviderDataSession}, heartbeats::HeartbeatReward, radio_threshold::VerifiedRadioThresholds, @@ -8,7 +8,7 @@ use crate::{ }; use chrono::{DateTime, Duration, Utc}; use file_store::traits::TimestampEncode; -use futures::{stream, Stream, StreamExt}; +use futures::{Stream, StreamExt}; use helium_crypto::PublicKeyBinary; use helium_proto::{ services::{ @@ -377,8 +377,79 @@ pub fn dc_to_mobile_bones(dc_amount: Decimal, mobile_bone_price: Decimal) -> Dec type RadioId = (PublicKeyBinary, Option); +fn make_heartbeat_trust_scores( + heartbeat: &HeartbeatReward, +) -> Vec { + let fallback: Vec = std::iter::repeat(0) + .take(heartbeat.trust_score_multipliers.len()) + .collect(); + heartbeat + .distances_to_asserted + .as_ref() + .unwrap_or(&fallback) + .iter() + .zip(heartbeat.trust_score_multipliers.iter()) + .map( + |(&distance, &trust_score)| coverage_point_calculator::LocationTrust { + meters_to_asserted: distance as u32, + trust_score, + }, + ) + .collect() +} + +pub fn coverage_point_to_mobile_reward_share( + coverage_points: coverage_point_calculator::CoveragePoints, + start_period: u64, + end_period: u64, + hotspot_key: &PublicKeyBinary, + cbsd_id: Option, + poc_reward: u64, + seniority_timestamp: DateTime, + coverage_object_uuid: Uuid, +) -> proto::MobileRewardShare { + let boosted_hexes = coverage_points + .covered_hexes + .iter() + .filter(|hex| hex.boosted_multiplier.is_some()) + .map(|covered_hex| proto::BoostedHex { + location: covered_hex.hex.into_raw(), + multiplier: covered_hex.boosted_multiplier.unwrap().to_u32().unwrap(), + }) + .collect(); + + let to_proto_value = |value: Decimal| (value * dec!(1000)).to_u32().unwrap_or_default(); + let location_trust_score_multiplier = to_proto_value(coverage_points.location_trust_multiplier); + let speedtest_multiplier = to_proto_value(coverage_points.speedtest_multiplier); + let coverage_points = coverage_points + .hex_coverage_points + .to_u64() + .unwrap_or_default(); + + let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); + + proto::MobileRewardShare { + start_period, + end_period, + reward: Some(proto::mobile_reward_share::Reward::RadioReward( + proto::RadioReward { + hotspot_key: hotspot_key.clone().into(), + cbsd_id: cbsd_id.unwrap_or_default(), + poc_reward, + coverage_points, + seniority_timestamp: seniority_timestamp.encode_timestamp(), + coverage_object, + location_trust_score_multiplier, + speedtest_multiplier, + boosted_hexes, + ..Default::default() + }, + )), + } +} + #[derive(Debug)] -pub struct CoveragePoints2 { +pub struct CoveragePoints { coverage_map: coverage_map::CoverageMap, // verified_radio_thresholds: VerifiedRadioThresholds, @@ -389,15 +460,19 @@ pub struct CoveragePoints2 { coverage_obj_uuid: HashMap, } -impl CoveragePoints2 { - pub async fn new( +impl CoveragePoints { + pub async fn aggregate_points( hex_streams: &impl CoveredHexStream, heartbeats: impl Stream>, - speedtest_averages: SpeedtestAverages, - boosted_hexes: BoostedHexes, - verified_radio_thresholds: VerifiedRadioThresholds, + speedtests: &SpeedtestAverages, + boosted_hexes: &BoostedHexes, + verified_radio_thresholds: &VerifiedRadioThresholds, reward_period: &Range>, ) -> anyhow::Result { + let speedtest_averages = speedtests.clone(); + let boosted_hexes = boosted_hexes.clone(); + let verified_radio_thresholds = verified_radio_thresholds.clone(); + let mut coverage_hash_map: HashMap> = HashMap::new(); let mut trust_scores: HashMap> = HashMap::new(); @@ -502,6 +577,7 @@ impl CoveragePoints2 { }) } + // =============================================================== fn radio_hexes(&self, radio_id: &RadioId) -> Vec { let (pubkey, cbsd_id) = radio_id; let ranked_coverage = match cbsd_id { @@ -579,110 +655,11 @@ impl CoveragePoints2 { Ok(coverage_points) } -} - -fn make_heartbeat_trust_scores( - heartbeat: &HeartbeatReward, -) -> Vec { - let fallback: Vec = std::iter::repeat(0) - .take(heartbeat.trust_score_multipliers.len()) - .collect(); - heartbeat - .distances_to_asserted - .as_ref() - .unwrap_or(&fallback) - .iter() - .zip(heartbeat.trust_score_multipliers.iter()) - .map( - |(&distance, &trust_score)| coverage_point_calculator::LocationTrust { - meters_to_asserted: distance as u32, - trust_score, - }, - ) - .collect() -} - -pub fn coverage_point_to_mobile_reward_share( - coverage_points: coverage_point_calculator::CoveragePoints, - start_period: u64, - end_period: u64, - hotspot_key: &PublicKeyBinary, - cbsd_id: Option, - poc_reward: u64, - seniority_timestamp: DateTime, - coverage_object_uuid: Uuid, -) -> proto::MobileRewardShare { - let boosted_hexes = coverage_points - .covered_hexes - .iter() - .filter(|hex| hex.boosted_multiplier.is_some()) - .map(|covered_hex| proto::BoostedHex { - location: covered_hex.hex.into_raw(), - multiplier: covered_hex.boosted_multiplier.unwrap().to_u32().unwrap(), - }) - .collect(); - - let to_proto_value = |value: Decimal| (value * dec!(1000)).to_u32().unwrap_or_default(); - let location_trust_score_multiplier = to_proto_value(coverage_points.location_trust_multiplier); - let speedtest_multiplier = to_proto_value(coverage_points.speedtest_multiplier); - let coverage_points = coverage_points - .hex_coverage_points - .to_u64() - .unwrap_or_default(); - - let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); - - proto::MobileRewardShare { - start_period, - end_period, - reward: Some(proto::mobile_reward_share::Reward::RadioReward( - proto::RadioReward { - hotspot_key: hotspot_key.clone().into(), - cbsd_id: cbsd_id.unwrap_or_default(), - poc_reward, - coverage_points, - seniority_timestamp: seniority_timestamp.encode_timestamp(), - coverage_object, - location_trust_score_multiplier, - speedtest_multiplier, - boosted_hexes, - ..Default::default() - }, - )), - } -} - -#[derive(Debug)] -pub struct CoveragePoints { - // coverage_points: HashMap, - coverage_points: CoveragePoints2, -} - -impl CoveragePoints { - pub async fn aggregate_points( - hex_streams: &impl CoveredHexStream, - heartbeats: impl Stream>, - speedtests: &SpeedtestAverages, - boosted_hexes: &BoostedHexes, - verified_radio_thresholds: &VerifiedRadioThresholds, - reward_period: &Range>, - ) -> anyhow::Result { - let coverage_points = CoveragePoints2::new( - hex_streams, - heartbeats, - speedtests.clone(), - boosted_hexes.clone(), - verified_radio_thresholds.clone(), - reward_period, - ) - .await?; - Ok(Self { coverage_points }) - } + // =============================================================== /// Only used for testing pub async fn hotspot_points(&mut self, hotspot: &RadioId) -> Decimal { - self.coverage_points - .coverage_points(hotspot) + self.coverage_points(hotspot) .await .expect("coverage points for hotspot") .total_coverage_points @@ -693,17 +670,13 @@ impl CoveragePoints { available_poc_rewards: Decimal, epoch: &'_ Range>, ) -> Option + '_> { - let radio_ids = self - .coverage_points - .all_radio_ids() - .await - .expect("radio ids"); + let radio_ids = self.all_radio_ids().await.expect("radio ids"); let mut total_shares: Decimal = dec!(0); let mut processed_radios = vec![]; for id in radio_ids { - let points_res = self.coverage_points.coverage_points(&id).await; + let points_res = self.coverage_points(&id).await; let points = match points_res { Ok(points) => points, Err(err) => { @@ -712,12 +685,8 @@ impl CoveragePoints { } }; - let seniority = self - .coverage_points - .get_seniority(&id) - .expect("seniority timestamp"); + let seniority = self.get_seniority(&id).expect("seniority timestamp"); let coverage_object_uuid = self - .coverage_points .get_coverage_object_uuid(&id) .expect("coverage_object_uuid"); @@ -2240,15 +2209,13 @@ mod test { ); let coverage_points = CoveragePoints { - coverage_points: CoveragePoints2 { - coverage_map, - verified_radio_thresholds: VerifiedRadioThresholds::default(), - speedtest_averages, - seniority_timestamps, - trust_scores, - radio_types, - coverage_obj_uuid, - }, + coverage_map, + verified_radio_thresholds: VerifiedRadioThresholds::default(), + speedtest_averages, + seniority_timestamps, + trust_scores, + radio_types, + coverage_obj_uuid, }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); // gw2 does not have enough speedtests for a mulitplier @@ -2272,16 +2239,14 @@ mod test { let now = Utc::now(); let epoch = now - Duration::hours(1)..now; let coverage_points = CoveragePoints { - coverage_points: CoveragePoints2 { - coverage_map: coverage_map::CoverageMapBuilder::default() - .build(&BoostedHexes::default(), epoch.start), - verified_radio_thresholds: VerifiedRadioThresholds::default(), - speedtest_averages: SpeedtestAverages::default(), - seniority_timestamps: HashMap::new(), - trust_scores: HashMap::new(), - radio_types: HashMap::new(), - coverage_obj_uuid: HashMap::new(), - }, + coverage_map: coverage_map::CoverageMapBuilder::default() + .build(&BoostedHexes::default(), epoch.start), + verified_radio_thresholds: VerifiedRadioThresholds::default(), + speedtest_averages: SpeedtestAverages::default(), + seniority_timestamps: HashMap::new(), + trust_scores: HashMap::new(), + radio_types: HashMap::new(), + coverage_obj_uuid: HashMap::new(), }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); From 055d4d48bdd5ae8585ffbc95cf7295f45e2ec90e Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Mon, 10 Jun 2024 09:29:32 -0700 Subject: [PATCH 21/40] starting to conslidate radio information into single map --- mobile_verifier/src/reward_shares.rs | 113 +++++++++++++++------------ 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 13d318af0..1912a4139 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -448,6 +448,16 @@ pub fn coverage_point_to_mobile_reward_share( } } +#[derive(Debug)] +struct RadioInfo { + radio_type: coverage_point_calculator::RadioType, + coverage_obj_uuid: Uuid, + indoor: bool, + trust_scores: Vec, + seniority_timestamps: Vec, + coverage: Vec, +} + #[derive(Debug)] pub struct CoveragePoints { coverage_map: coverage_map::CoverageMap, @@ -458,6 +468,8 @@ pub struct CoveragePoints { seniority_timestamps: HashMap>, radio_types: HashMap, coverage_obj_uuid: HashMap, + // + radio_infos: HashMap, } impl CoveragePoints { @@ -473,50 +485,42 @@ impl CoveragePoints { let boosted_hexes = boosted_hexes.clone(); let verified_radio_thresholds = verified_radio_thresholds.clone(); - let mut coverage_hash_map: HashMap> = HashMap::new(); - let mut trust_scores: HashMap> = - HashMap::new(); - let mut coverage_obj_uuid: HashMap = HashMap::new(); - let mut seniority_timestamps: HashMap> = HashMap::new(); - let mut radio_types: HashMap = + let trust_scores: HashMap> = HashMap::new(); + let coverage_obj_uuid: HashMap = HashMap::new(); + let seniority_timestamps: HashMap> = HashMap::new(); + let radio_types: HashMap = HashMap::new(); + + let mut radio_infos: HashMap = HashMap::new(); let mut heartbeats = std::pin::pin!(heartbeats); while let Some(heartbeat) = heartbeats.next().await.transpose()? { let pubkey = heartbeat.hotspot_key.clone(); - let heartbeat_key = heartbeat.key().clone(); + let heartbeat_key = heartbeat.key(); let cbsd_id = heartbeat_key.to_owned().into_cbsd_id(); let key = (pubkey.clone(), cbsd_id.clone()); let seniority = hex_streams .fetch_seniority(heartbeat_key, reward_period.end) .await?; - seniority_timestamps - .entry(key.clone()) - .or_default() - .push(seniority.clone()); - - trust_scores - .entry(key.clone()) - .or_default() - .extend(make_heartbeat_trust_scores(&heartbeat)); - coverage_obj_uuid.insert(key.clone(), heartbeat.coverage_object); + let (is_indoor, covered_hexes) = { + let covered_hexes_stream = hex_streams + .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) + .await?; - let covered_hexes_stream = hex_streams - .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) - .await?; + let mut covered_hexes = vec![]; - // FIXME: Is there a way to make is_indoor look nicer? - let mut is_indoor = false; - let mut covered_hexes = std::pin::pin!(covered_hexes_stream); - while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { - is_indoor = hex_coverage.indoor; - coverage_hash_map - .entry(key.clone()) - .or_default() - .push(hex_coverage); - } + // FIXME: Is there a way to make is_indoor look nicer? + let mut is_indoor = false; + let mut covered_hexes_stream = std::pin::pin!(covered_hexes_stream); + while let Some(hex_coverage) = covered_hexes_stream.next().await.transpose()? { + is_indoor = hex_coverage.indoor; + covered_hexes.push(hex_coverage); + } + + (is_indoor, covered_hexes) + }; let radio_type = match (is_indoor, cbsd_id.is_some()) { (true, false) => coverage_point_calculator::RadioType::IndoorWifi, @@ -524,28 +528,37 @@ impl CoveragePoints { (false, false) => coverage_point_calculator::RadioType::OutdoorWifi, (false, true) => coverage_point_calculator::RadioType::OutdoorCbrs, }; - radio_types.insert(key.clone(), radio_type); + + let radio_info = radio_infos.entry(key).or_insert_with(|| RadioInfo { + radio_type, + coverage_obj_uuid: heartbeat.coverage_object, + indoor: is_indoor, + trust_scores: vec![], + seniority_timestamps: vec![], + coverage: vec![], + }); + + radio_info + .trust_scores + .extend(make_heartbeat_trust_scores(&heartbeat)); + radio_info.seniority_timestamps.push(seniority); + radio_info.coverage.extend(covered_hexes); } let coverage_map = { let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); - for (key, coverage) in &coverage_hash_map { + for (key, radio_info) in &radio_infos { let seniority = { - let mut seniorities = seniority_timestamps - .get(&key) - .expect("seniority gauranteed") - .clone(); + let mut seniorities = radio_info.seniority_timestamps.clone(); seniorities.sort_by_key(|s| s.seniority_ts); let first = seniorities.first().cloned(); first.expect("seniority gauranteed") }; - let is_indoor = coverage.first().unwrap().indoor; - let (pubkey, cbsd_id) = &key; - - let coverage = coverage + let coverage = radio_info + .coverage + .clone() .into_iter() - .cloned() .map(|hex_coverage| coverage_map::UnrankedCoverage { location: hex_coverage.hex, signal_power: hex_coverage.signal_power, @@ -555,14 +568,15 @@ impl CoveragePoints { .collect(); let coverage_obj = coverage_map::CoverageObject { - indoor: is_indoor, - hotspot_key: pubkey.clone().into(), - cbsd_id: cbsd_id.clone(), + indoor: radio_info.indoor, + hotspot_key: key.0.clone().into(), + cbsd_id: key.1.clone(), seniority_timestamp: seniority.seniority_ts, coverage, }; coverage_map_builder.insert_coverage_object(coverage_obj); } + coverage_map_builder.build(&boosted_hexes, reward_period.start) }; @@ -574,6 +588,7 @@ impl CoveragePoints { seniority_timestamps, radio_types, coverage_obj_uuid, + radio_infos, }) } @@ -584,7 +599,7 @@ impl CoveragePoints { Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), }; - ranked_coverage.to_vec().into_iter().collect() + ranked_coverage.to_vec() } pub async fn all_radio_ids(&mut self) -> anyhow::Result> { @@ -636,14 +651,14 @@ impl CoveragePoints { }); } - let radio_type = self.radio_type(&radio_id); + let radio_type = self.radio_type(radio_id); let trust_scores = self .trust_scores - .get(&radio_id) + .get(radio_id) .expect("trust scores") .clone(); - let verified = self.radio_threshold_verified(&radio_id); - let hexes = self.radio_hexes(&radio_id); + let verified = self.radio_threshold_verified(radio_id); + let hexes = self.radio_hexes(radio_id); let coverage_points = coverage_point_calculator::CoveragePoints::new( radio_type, From f3afac97df39542a379bb8634d28534a55af0719 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Mon, 10 Jun 2024 15:08:35 -0700 Subject: [PATCH 22/40] total coverage points is now provided pre-truncated --- mobile_verifier/src/reward_shares.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 1912a4139..3da01c021 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -421,10 +421,8 @@ pub fn coverage_point_to_mobile_reward_share( let to_proto_value = |value: Decimal| (value * dec!(1000)).to_u32().unwrap_or_default(); let location_trust_score_multiplier = to_proto_value(coverage_points.location_trust_multiplier); let speedtest_multiplier = to_proto_value(coverage_points.speedtest_multiplier); - let coverage_points = coverage_points - .hex_coverage_points - .to_u64() - .unwrap_or_default(); + + let coverage_points = coverage_points.total_coverage_points; let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); From e025f68890fb951d17e83f9394acc895e874175b Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Mon, 10 Jun 2024 17:25:21 -0700 Subject: [PATCH 23/40] simplify coverage points to use only radio_infos --- mobile_verifier/src/reward_shares.rs | 396 ++++++++++----------------- 1 file changed, 142 insertions(+), 254 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 3da01c021..1e712f4f8 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1,5 +1,5 @@ use crate::{ - coverage::{CoveredHexStream, HexCoverage, Seniority}, + coverage::{CoveredHexStream, Seniority}, data_session::{HotspotMap, ServiceProviderDataSession}, heartbeats::HeartbeatReward, radio_threshold::VerifiedRadioThresholds, @@ -400,14 +400,14 @@ fn make_heartbeat_trust_scores( pub fn coverage_point_to_mobile_reward_share( coverage_points: coverage_point_calculator::CoveragePoints, - start_period: u64, - end_period: u64, - hotspot_key: &PublicKeyBinary, - cbsd_id: Option, + reward_epoch: &Range>, + radio_id: &RadioId, poc_reward: u64, seniority_timestamp: DateTime, coverage_object_uuid: Uuid, ) -> proto::MobileRewardShare { + let (hotspot_key, cbsd_id) = radio_id.clone(); + let boosted_hexes = coverage_points .covered_hexes .iter() @@ -422,16 +422,19 @@ pub fn coverage_point_to_mobile_reward_share( let location_trust_score_multiplier = to_proto_value(coverage_points.location_trust_multiplier); let speedtest_multiplier = to_proto_value(coverage_points.speedtest_multiplier); - let coverage_points = coverage_points.total_coverage_points; + let coverage_points = coverage_points + .total_coverage_points + .to_u64() + .unwrap_or_default(); let coverage_object = Vec::from(coverage_object_uuid.into_bytes()); proto::MobileRewardShare { - start_period, - end_period, + start_period: reward_epoch.start.encode_timestamp(), + end_period: reward_epoch.end.encode_timestamp(), reward: Some(proto::mobile_reward_share::Reward::RadioReward( proto::RadioReward { - hotspot_key: hotspot_key.clone().into(), + hotspot_key: hotspot_key.into(), cbsd_id: cbsd_id.unwrap_or_default(), poc_reward, coverage_points, @@ -446,27 +449,19 @@ pub fn coverage_point_to_mobile_reward_share( } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct RadioInfo { radio_type: coverage_point_calculator::RadioType, coverage_obj_uuid: Uuid, - indoor: bool, + seniority: Seniority, trust_scores: Vec, - seniority_timestamps: Vec, - coverage: Vec, + verified_radio_threshold: coverage_point_calculator::RadioThreshold, + speedtests: Vec, } #[derive(Debug)] pub struct CoveragePoints { coverage_map: coverage_map::CoverageMap, - // - verified_radio_thresholds: VerifiedRadioThresholds, - speedtest_averages: SpeedtestAverages, - trust_scores: HashMap>, - seniority_timestamps: HashMap>, - radio_types: HashMap, - coverage_obj_uuid: HashMap, - // radio_infos: HashMap, } @@ -479,18 +474,10 @@ impl CoveragePoints { verified_radio_thresholds: &VerifiedRadioThresholds, reward_period: &Range>, ) -> anyhow::Result { - let speedtest_averages = speedtests.clone(); - let boosted_hexes = boosted_hexes.clone(); - let verified_radio_thresholds = verified_radio_thresholds.clone(); - - let trust_scores: HashMap> = - HashMap::new(); - let coverage_obj_uuid: HashMap = HashMap::new(); - let seniority_timestamps: HashMap> = HashMap::new(); - let radio_types: HashMap = HashMap::new(); - let mut radio_infos: HashMap = HashMap::new(); + let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); + // The heartbearts query is written in a way that each rardio is iterated a single time. let mut heartbeats = std::pin::pin!(heartbeats); while let Some(heartbeat) = heartbeats.next().await.transpose()? { let pubkey = heartbeat.hotspot_key.clone(); @@ -514,7 +501,12 @@ impl CoveragePoints { let mut covered_hexes_stream = std::pin::pin!(covered_hexes_stream); while let Some(hex_coverage) = covered_hexes_stream.next().await.transpose()? { is_indoor = hex_coverage.indoor; - covered_hexes.push(hex_coverage); + covered_hexes.push(coverage_map::UnrankedCoverage { + location: hex_coverage.hex, + signal_power: hex_coverage.signal_power, + signal_level: hex_coverage.signal_level.into(), + assignments: hex_coverage.assignments, + }); } (is_indoor, covered_hexes) @@ -527,136 +519,73 @@ impl CoveragePoints { (false, true) => coverage_point_calculator::RadioType::OutdoorCbrs, }; - let radio_info = radio_infos.entry(key).or_insert_with(|| RadioInfo { - radio_type, - coverage_obj_uuid: heartbeat.coverage_object, + coverage_map_builder.insert_coverage_object(coverage_map::CoverageObject { indoor: is_indoor, - trust_scores: vec![], - seniority_timestamps: vec![], - coverage: vec![], + hotspot_key: pubkey.clone().into(), + cbsd_id: cbsd_id.clone(), + seniority_timestamp: seniority.seniority_ts, + coverage: covered_hexes, }); - radio_info - .trust_scores - .extend(make_heartbeat_trust_scores(&heartbeat)); - radio_info.seniority_timestamps.push(seniority); - radio_info.coverage.extend(covered_hexes); - } - - let coverage_map = { - let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); - for (key, radio_info) in &radio_infos { - let seniority = { - let mut seniorities = radio_info.seniority_timestamps.clone(); - seniorities.sort_by_key(|s| s.seniority_ts); - let first = seniorities.first().cloned(); - first.expect("seniority gauranteed") - }; + use coverage_point_calculator::{BytesPs, Speedtest}; + let speedtests = speedtests + .get_average(&pubkey) + .unwrap() + .speedtests + .iter() + .map(|test| Speedtest { + upload_speed: BytesPs::new(test.report.upload_speed), + download_speed: BytesPs::new(test.report.download_speed), + latency_millis: test.report.latency, + timestamp: test.report.timestamp, + }) + .collect(); - let coverage = radio_info - .coverage - .clone() - .into_iter() - .map(|hex_coverage| coverage_map::UnrankedCoverage { - location: hex_coverage.hex, - signal_power: hex_coverage.signal_power, - signal_level: hex_coverage.signal_level.into(), - assignments: hex_coverage.assignments, - }) - .collect(); - - let coverage_obj = coverage_map::CoverageObject { - indoor: radio_info.indoor, - hotspot_key: key.0.clone().into(), - cbsd_id: key.1.clone(), - seniority_timestamp: seniority.seniority_ts, - coverage, + let verified_radio_threshold = + match verified_radio_thresholds.is_verified(pubkey, cbsd_id) { + true => coverage_point_calculator::RadioThreshold::Verified, + false => coverage_point_calculator::RadioThreshold::Unverified, }; - coverage_map_builder.insert_coverage_object(coverage_obj); - } + radio_infos.insert( + key, + RadioInfo { + radio_type, + coverage_obj_uuid: heartbeat.coverage_object, + seniority, + trust_scores: make_heartbeat_trust_scores(&heartbeat), + verified_radio_threshold, + speedtests, + }, + ); + } - coverage_map_builder.build(&boosted_hexes, reward_period.start) - }; + let coverage_map = coverage_map_builder.build(boosted_hexes, reward_period.start); Ok(Self { coverage_map, - verified_radio_thresholds, - speedtest_averages, - trust_scores, - seniority_timestamps, - radio_types, - coverage_obj_uuid, radio_infos, }) } - // =============================================================== - fn radio_hexes(&self, radio_id: &RadioId) -> Vec { - let (pubkey, cbsd_id) = radio_id; - let ranked_coverage = match cbsd_id { - Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), - None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), - }; - ranked_coverage.to_vec() - } - - pub async fn all_radio_ids(&mut self) -> anyhow::Result> { - Ok(self.radio_types.keys().cloned().collect()) - } - - pub fn get_seniority(&self, key: &RadioId) -> Option { - let mut s = self - .seniority_timestamps - .get(key) - .expect("seniority timestamps") - .clone(); - s.sort_by_key(|f| f.seniority_ts); - s.first().cloned() - } - - fn radio_type(&self, key: &RadioId) -> coverage_point_calculator::RadioType { - self.radio_types.get(key).cloned().expect("radio type") - } - - fn radio_threshold_verified(&self, key: &RadioId) -> coverage_point_calculator::RadioThreshold { - match self - .verified_radio_thresholds - .is_verified(key.0.clone(), key.1.clone()) - { - true => coverage_point_calculator::RadioThreshold::Verified, - false => coverage_point_calculator::RadioThreshold::Unverified, - } - } - - fn get_coverage_object_uuid(&self, key: &RadioId) -> Option { - self.coverage_obj_uuid.get(key).cloned() - } - - pub async fn coverage_points( - &mut self, + async fn coverage_points( + &self, radio_id: &RadioId, + radio_info: RadioInfo, ) -> anyhow::Result { - let mut speedtests = vec![]; - let pubkey = &radio_id.0; - let average = self.speedtest_averages.get_average(pubkey).unwrap(); - for test in average.speedtests { - use coverage_point_calculator::{BytesPs, Speedtest}; - speedtests.push(Speedtest { - upload_speed: BytesPs::new(test.report.upload_speed), - download_speed: BytesPs::new(test.report.download_speed), - latency_millis: test.report.latency, - timestamp: test.report.timestamp, - }); - } - - let radio_type = self.radio_type(radio_id); - let trust_scores = self - .trust_scores - .get(radio_id) - .expect("trust scores") - .clone(); - let verified = self.radio_threshold_verified(radio_id); - let hexes = self.radio_hexes(radio_id); + let radio_type = radio_info.radio_type; + let trust_scores = radio_info.trust_scores; + let verified = radio_info.verified_radio_threshold; + let speedtests = radio_info.speedtests; + + let hexes = { + let this = &self; + let (pubkey, cbsd_id) = radio_id; + let ranked_coverage = match cbsd_id { + Some(cbsd_id) => this.coverage_map.get_cbrs_coverage(cbsd_id), + None => this.coverage_map.get_wifi_coverage(pubkey.as_ref()), + }; + ranked_coverage.to_vec() + }; let coverage_points = coverage_point_calculator::CoveragePoints::new( radio_type, @@ -672,24 +601,24 @@ impl CoveragePoints { /// Only used for testing pub async fn hotspot_points(&mut self, hotspot: &RadioId) -> Decimal { - self.coverage_points(hotspot) + let radio = self.radio_infos.get(hotspot).unwrap(); + + self.coverage_points(hotspot, radio.clone()) .await .expect("coverage points for hotspot") .total_coverage_points } pub async fn into_rewards( - mut self, + self, available_poc_rewards: Decimal, epoch: &'_ Range>, ) -> Option + '_> { - let radio_ids = self.all_radio_ids().await.expect("radio ids"); - let mut total_shares: Decimal = dec!(0); let mut processed_radios = vec![]; - for id in radio_ids { - let points_res = self.coverage_points(&id).await; + for (id, radio_info) in self.radio_infos.iter() { + let points_res = self.coverage_points(id, radio_info.clone()).await; let points = match points_res { Ok(points) => points, Err(err) => { @@ -698,13 +627,11 @@ impl CoveragePoints { } }; - let seniority = self.get_seniority(&id).expect("seniority timestamp"); - let coverage_object_uuid = self - .get_coverage_object_uuid(&id) - .expect("coverage_object_uuid"); + let seniority = radio_info.seniority.clone(); + let coverage_object_uuid = radio_info.coverage_obj_uuid; - total_shares += points.total_coverage_points; - processed_radios.push((id, points, seniority, coverage_object_uuid)); + total_shares += points.reward_shares; + processed_radios.push((id.clone(), points, seniority, coverage_object_uuid)); } let Some(rewards_per_share) = available_poc_rewards.checked_div(total_shares) else { @@ -716,18 +643,13 @@ impl CoveragePoints { processed_radios .into_iter() .map(move |(id, points, seniority, coverage_object_uuid)| { - let poc_reward = rewards_per_share * points.total_coverage_points; - let poc_reward = poc_reward - .round_dp_with_strategy(0, RoundingStrategy::ToZero) - .to_u64() - .unwrap_or_default(); + let poc_reward = rewards_per_share * points.reward_shares; + let poc_reward = poc_reward.to_u64().unwrap_or_default(); let mobile_reward_share = coverage_point_to_mobile_reward_share( points, - epoch.start.encode_timestamp(), - epoch.end.encode_timestamp(), - &id.0, - id.1, + epoch, + &id, poc_reward, seniority.seniority_ts, coverage_object_uuid, @@ -2115,63 +2037,6 @@ mod test { let uuid_1 = Uuid::new_v4(); let uuid_2 = Uuid::new_v4(); - let mut radio_types: HashMap< - (PublicKeyBinary, Option), - coverage_point_calculator::RadioType, - > = HashMap::new(); - radio_types.insert( - (gw1.clone(), None), - coverage_point_calculator::RadioType::IndoorWifi, - ); - radio_types.insert( - (gw2.clone(), None), - coverage_point_calculator::RadioType::IndoorWifi, - ); - - let mut radio_heartbeats = HashMap::>::new(); - radio_heartbeats.insert( - (gw1.clone(), None), - vec![HeartbeatReward { - hotspot_key: gw1.clone(), - cbsd_id: None, - cell_type: CellType::NovaGenericWifiIndoor, - distances_to_asserted: None, - trust_score_multipliers: vec![dec!(1)], - coverage_object: uuid_1, - }], - ); - radio_heartbeats.insert( - (gw2.clone(), None), - vec![HeartbeatReward { - hotspot_key: gw2.clone(), - cbsd_id: None, - cell_type: CellType::NovaGenericWifiIndoor, - distances_to_asserted: None, - trust_score_multipliers: vec![dec!(1)], - coverage_object: uuid_2, - }], - ); - - let mut coverage_obj_uuid = HashMap::new(); - coverage_obj_uuid.insert((gw1.clone(), None), uuid_1); - coverage_obj_uuid.insert((gw2.clone(), None), uuid_2); - - let mut trust_scores = HashMap::new(); - trust_scores.insert( - (gw1.clone(), None), - vec![coverage_point_calculator::LocationTrust { - meters_to_asserted: 0, - trust_score: dec!(1), - }], - ); - trust_scores.insert( - (gw2.clone(), None), - vec![coverage_point_calculator::LocationTrust { - meters_to_asserted: 0, - trust_score: dec!(1), - }], - ); - let mut coverage_map = coverage_map::CoverageMapBuilder::default(); coverage_map.insert_coverage_object(coverage_map::CoverageObject { indoor: true, @@ -2199,36 +2064,64 @@ mod test { }); let coverage_map = coverage_map.build(&BoostedHexes::default(), epoch.start); - let mut seniority_timestamps = HashMap::new(); - seniority_timestamps.insert( + let mut radio_infos = HashMap::new(); + radio_infos.insert( (gw1.clone(), None), - vec![Seniority { - uuid: Uuid::new_v4(), - seniority_ts: now.clone(), - last_heartbeat: now.clone(), - inserted_at: now.clone(), - update_reason: 0, - }], + RadioInfo { + radio_type: coverage_point_calculator::RadioType::IndoorWifi, + coverage_obj_uuid: uuid_1, + trust_scores: vec![coverage_point_calculator::LocationTrust { + meters_to_asserted: 0, + trust_score: dec!(1), + }], + seniority: Seniority { + uuid: Uuid::new_v4(), + seniority_ts: now.clone(), + last_heartbeat: now.clone(), + inserted_at: now.clone(), + update_reason: 0, + }, + verified_radio_threshold: coverage_point_calculator::RadioThreshold::Verified, + speedtests: vec![ + coverage_point_calculator::Speedtest { + upload_speed: coverage_point_calculator::BytesPs::new(100_000_000), + download_speed: coverage_point_calculator::BytesPs::new(100_000_000), + latency_millis: 10, + timestamp: now.clone(), + }, + coverage_point_calculator::Speedtest { + upload_speed: coverage_point_calculator::BytesPs::new(100_000_000), + download_speed: coverage_point_calculator::BytesPs::new(100_000_000), + latency_millis: 10, + timestamp: now.clone(), + }, + ], + }, ); - seniority_timestamps.insert( + radio_infos.insert( (gw2.clone(), None), - vec![Seniority { - uuid: Uuid::new_v4(), - seniority_ts: now.clone(), - last_heartbeat: now.clone(), - inserted_at: now.clone(), - update_reason: 0, - }], + RadioInfo { + radio_type: coverage_point_calculator::RadioType::IndoorWifi, + coverage_obj_uuid: uuid_2, + trust_scores: vec![coverage_point_calculator::LocationTrust { + meters_to_asserted: 0, + trust_score: dec!(1), + }], + seniority: Seniority { + uuid: Uuid::new_v4(), + seniority_ts: now.clone(), + last_heartbeat: now.clone(), + inserted_at: now.clone(), + update_reason: 0, + }, + verified_radio_threshold: coverage_point_calculator::RadioThreshold::Verified, + speedtests: vec![], + }, ); let coverage_points = CoveragePoints { coverage_map, - verified_radio_thresholds: VerifiedRadioThresholds::default(), - speedtest_averages, - seniority_timestamps, - trust_scores, - radio_types, - coverage_obj_uuid, + radio_infos, }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); // gw2 does not have enough speedtests for a mulitplier @@ -2254,12 +2147,7 @@ mod test { let coverage_points = CoveragePoints { coverage_map: coverage_map::CoverageMapBuilder::default() .build(&BoostedHexes::default(), epoch.start), - verified_radio_thresholds: VerifiedRadioThresholds::default(), - speedtest_averages: SpeedtestAverages::default(), - seniority_timestamps: HashMap::new(), - trust_scores: HashMap::new(), - radio_types: HashMap::new(), - coverage_obj_uuid: HashMap::new(), + radio_infos: HashMap::new(), }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); From 611422124e9b25cace96bab5112e5bf08d34e160 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 12 Jun 2024 16:48:39 -0700 Subject: [PATCH 24/40] hotspot_points no longer needs to be &mut self --- mobile_verifier/src/reward_shares.rs | 2 +- .../tests/integrations/boosting_oracles.rs | 10 ++++++++-- .../tests/integrations/modeled_coverage.rs | 12 ++++++------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 1e712f4f8..dd31f1af9 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -600,7 +600,7 @@ impl CoveragePoints { // =============================================================== /// Only used for testing - pub async fn hotspot_points(&mut self, hotspot: &RadioId) -> Decimal { + pub async fn hotspot_points(&self, hotspot: &RadioId) -> Decimal { let radio = self.radio_infos.get(hotspot).unwrap(); self.coverage_points(hotspot, radio.clone()) diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index a18fdeaec..c04d1983c 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -350,7 +350,8 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re .build()?; let _ = common::set_unassigned_oracle_boosting_assignments(&pool, &hex_boost_data).await?; - let heartbeats = heartbeats(12, start, &owner, &cbsd_id, 0.0, 0.0, uuid); + let heartbeat_owner = owner.clone(); + let heartbeats = heartbeats(12, start, &heartbeat_owner, &cbsd_id, 0.0, 0.0, uuid); let coverage_objects = CoverageObjectCache::new(&pool); let coverage_claim_time_cache = CoverageClaimTimeCache::new(); @@ -450,7 +451,12 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re // ----------------------------------------------- // = 1,073 - assert_eq!(coverage_points.hotspot_points(&owner), dec!(1073.0)); + assert_eq!( + coverage_points + .hotspot_points(&(owner, Some(cbsd_id.clone()))) + .await, + dec!(1073.0) + ); Ok(()) } diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index 8954f2c7a..28ed6d257 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -492,7 +492,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -596,7 +596,7 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -888,7 +888,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -989,7 +989,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -1092,7 +1092,7 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, @@ -1345,7 +1345,7 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let mut coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::aggregate_points( &pool, heartbeats, &speedtest_avgs, From 1ad133afedbd6780a6eaf30f02544a4e60a7495f Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 12 Jun 2024 18:09:02 -0700 Subject: [PATCH 25/40] clean up trust_score construction and long types Move zipping trust scores with their distances to heartbeatreward. Leave I put the use statements above where they are in the function because the names of the types kind of overlap with the names used in the mobile verifier, and I wanted to make it extra clear to anyone reading, this function is transforming those values into calculator types. --- mobile_verifier/src/heartbeats/mod.rs | 12 ++++++ mobile_verifier/src/reward_shares.rs | 60 +++++++++++---------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index 6352b6788..fd2a7c0a5 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -342,6 +342,18 @@ impl HeartbeatReward { .bind(MINIMUM_HEARTBEAT_COUNT) .fetch(exec) } + + pub fn iter_distances_and_scores(&self) -> impl Iterator { + let fallback: Vec = std::iter::repeat(0) + .take(self.trust_score_multipliers.len()) + .collect(); + + self.distances_to_asserted + .clone() + .unwrap_or(fallback) + .into_iter() + .zip(self.trust_score_multipliers.clone()) + } } #[derive(Clone)] diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index dd31f1af9..8edd7bd71 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -375,29 +375,6 @@ pub fn dc_to_mobile_bones(dc_amount: Decimal, mobile_bone_price: Decimal) -> Dec .round_dp_with_strategy(DEFAULT_PREC, RoundingStrategy::ToPositiveInfinity) } -type RadioId = (PublicKeyBinary, Option); - -fn make_heartbeat_trust_scores( - heartbeat: &HeartbeatReward, -) -> Vec { - let fallback: Vec = std::iter::repeat(0) - .take(heartbeat.trust_score_multipliers.len()) - .collect(); - heartbeat - .distances_to_asserted - .as_ref() - .unwrap_or(&fallback) - .iter() - .zip(heartbeat.trust_score_multipliers.iter()) - .map( - |(&distance, &trust_score)| coverage_point_calculator::LocationTrust { - meters_to_asserted: distance as u32, - trust_score, - }, - ) - .collect() -} - pub fn coverage_point_to_mobile_reward_share( coverage_points: coverage_point_calculator::CoveragePoints, reward_epoch: &Range>, @@ -411,7 +388,7 @@ pub fn coverage_point_to_mobile_reward_share( let boosted_hexes = coverage_points .covered_hexes .iter() - .filter(|hex| hex.boosted_multiplier.is_some()) + .filter(|hex| hex.boosted_multiplier.is_some_and(|boost| boost > dec!(1))) .map(|covered_hex| proto::BoostedHex { location: covered_hex.hex.into_raw(), multiplier: covered_hex.boosted_multiplier.unwrap().to_u32().unwrap(), @@ -449,6 +426,8 @@ pub fn coverage_point_to_mobile_reward_share( } } +type RadioId = (PublicKeyBinary, Option); + #[derive(Debug, Clone)] struct RadioInfo { radio_type: coverage_point_calculator::RadioType, @@ -477,7 +456,7 @@ impl CoveragePoints { let mut radio_infos: HashMap = HashMap::new(); let mut coverage_map_builder = coverage_map::CoverageMapBuilder::default(); - // The heartbearts query is written in a way that each rardio is iterated a single time. + // The heartbearts query is written in a way that each radio is iterated a single time. let mut heartbeats = std::pin::pin!(heartbeats); while let Some(heartbeat) = heartbeats.next().await.transpose()? { let pubkey = heartbeat.hotspot_key.clone(); @@ -496,7 +475,6 @@ impl CoveragePoints { let mut covered_hexes = vec![]; - // FIXME: Is there a way to make is_indoor look nicer? let mut is_indoor = false; let mut covered_hexes_stream = std::pin::pin!(covered_hexes_stream); while let Some(hex_coverage) = covered_hexes_stream.next().await.transpose()? { @@ -512,11 +490,12 @@ impl CoveragePoints { (is_indoor, covered_hexes) }; - let radio_type = match (is_indoor, cbsd_id.is_some()) { - (true, false) => coverage_point_calculator::RadioType::IndoorWifi, - (true, true) => coverage_point_calculator::RadioType::IndoorCbrs, - (false, false) => coverage_point_calculator::RadioType::OutdoorWifi, - (false, true) => coverage_point_calculator::RadioType::OutdoorCbrs, + use coverage_point_calculator::RadioType; + let radio_type = match (is_indoor, cbsd_id.as_ref()) { + (true, None) => RadioType::IndoorWifi, + (true, Some(_)) => RadioType::IndoorCbrs, + (false, None) => RadioType::OutdoorWifi, + (false, Some(_)) => RadioType::OutdoorCbrs, }; coverage_map_builder.insert_coverage_object(coverage_map::CoverageObject { @@ -541,18 +520,29 @@ impl CoveragePoints { }) .collect(); + use coverage_point_calculator::RadioThreshold; let verified_radio_threshold = match verified_radio_thresholds.is_verified(pubkey, cbsd_id) { - true => coverage_point_calculator::RadioThreshold::Verified, - false => coverage_point_calculator::RadioThreshold::Unverified, + true => RadioThreshold::Verified, + false => RadioThreshold::Unverified, }; + + use coverage_point_calculator::LocationTrust; + let trust_scores = heartbeat + .iter_distances_and_scores() + .map(|(distance, trust_score)| LocationTrust { + meters_to_asserted: distance as u32, + trust_score, + }) + .collect(); + radio_infos.insert( key, RadioInfo { radio_type, coverage_obj_uuid: heartbeat.coverage_object, seniority, - trust_scores: make_heartbeat_trust_scores(&heartbeat), + trust_scores, verified_radio_threshold, speedtests, }, @@ -606,7 +596,7 @@ impl CoveragePoints { self.coverage_points(hotspot, radio.clone()) .await .expect("coverage points for hotspot") - .total_coverage_points + .reward_shares } pub async fn into_rewards( From a7d642271f2c50f0e42e15deabd3021ac07ae2bb Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 12 Jun 2024 18:09:28 -0700 Subject: [PATCH 26/40] name test function more explicitly --- mobile_verifier/src/reward_shares.rs | 2 +- .../tests/integrations/boosting_oracles.rs | 2 +- .../tests/integrations/modeled_coverage.rs | 36 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 8edd7bd71..5549c4ce7 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -590,7 +590,7 @@ impl CoveragePoints { // =============================================================== /// Only used for testing - pub async fn hotspot_points(&self, hotspot: &RadioId) -> Decimal { + pub async fn test_hotspot_reward_shares(&self, hotspot: &RadioId) -> Decimal { let radio = self.radio_infos.get(hotspot).unwrap(); self.coverage_points(hotspot, radio.clone()) diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index c04d1983c..c50d1e789 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -453,7 +453,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re assert_eq!( coverage_points - .hotspot_points(&(owner, Some(cbsd_id.clone()))) + .test_hotspot_reward_shares(&(owner, Some(cbsd_id.clone()))) .await, dec!(1073.0) ); diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index 28ed6d257..e113d6b96 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -504,7 +504,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner, Some(cbsd_id))) + .test_hotspot_reward_shares(&(owner, Some(cbsd_id))) .await, dec!(250) ); @@ -608,13 +608,13 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) .await, dec!(112.5) ); assert_eq!( coverage_points - .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) .await, dec!(250) ); @@ -900,37 +900,37 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_3, Some(cbsd_id_3))) + .test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_4, Some(cbsd_id_4))) + .test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))) .await, dec!(250) ); assert_eq!( coverage_points - .hotspot_points(&(owner_5, Some(cbsd_id_5))) + .test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_6, Some(cbsd_id_6))) + .test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))) .await, dec!(0) ); @@ -1001,7 +1001,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner, Some(cbsd_id))) + .test_hotspot_reward_shares(&(owner, Some(cbsd_id))) .await, dec!(19) ); @@ -1104,13 +1104,13 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) .await, dec!(19) * dec!(0.5) ); assert_eq!( coverage_points - .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) .await, dec!(8) ); @@ -1357,37 +1357,37 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { assert_eq!( coverage_points - .hotspot_points(&(owner_1, Some(cbsd_id_1))) + .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_2, Some(cbsd_id_2))) + .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) .await, dec!(62.5) ); assert_eq!( coverage_points - .hotspot_points(&(owner_3, Some(cbsd_id_3))) + .test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_4, Some(cbsd_id_4))) + .test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_5, Some(cbsd_id_5))) + .test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))) .await, dec!(0) ); assert_eq!( coverage_points - .hotspot_points(&(owner_6, Some(cbsd_id_6))) + .test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))) .await, dec!(0) ); From d2112f4827deedd489a308c4d885bd7d95a50d5d Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 12 Jun 2024 18:10:30 -0700 Subject: [PATCH 27/40] DateTime implements Copy, no need to Clone --- mobile_verifier/src/reward_shares.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 5549c4ce7..d029d3ca0 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1990,7 +1990,7 @@ mod test { report: CellSpeedtest { pubkey: gw1.clone(), serial: "serial-1".to_string(), - timestamp: now.clone(), + timestamp: now, upload_speed: 100_000_000, download_speed: 100_000_000, latency: 10, @@ -2000,7 +2000,7 @@ mod test { report: CellSpeedtest { pubkey: gw1.clone(), serial: "serial-1".to_string(), - timestamp: now.clone(), + timestamp: now, upload_speed: 100_000_000, download_speed: 100_000_000, latency: 10, @@ -2014,7 +2014,7 @@ mod test { report: CellSpeedtest { pubkey: gw2.clone(), serial: "serial-2".to_string(), - timestamp: now.clone(), + timestamp: now, upload_speed: 100_000_000, download_speed: 100_000_000, latency: 10, @@ -2032,7 +2032,7 @@ mod test { indoor: true, hotspot_key: gw1.clone().into(), cbsd_id: None, - seniority_timestamp: now.clone(), + seniority_timestamp: now, coverage: vec![coverage_map::UnrankedCoverage { location: Cell::from_raw(0x8c2681a3064dbff).expect("valid h3 cell"), signal_power: 42, @@ -2044,7 +2044,7 @@ mod test { indoor: true, hotspot_key: gw2.clone().into(), cbsd_id: None, - seniority_timestamp: now.clone(), + seniority_timestamp: now, coverage: vec![coverage_map::UnrankedCoverage { location: Cell::from_raw(0x8c2681a3064ddff).expect("valid h3 cell"), signal_power: 42, @@ -2066,9 +2066,9 @@ mod test { }], seniority: Seniority { uuid: Uuid::new_v4(), - seniority_ts: now.clone(), - last_heartbeat: now.clone(), - inserted_at: now.clone(), + seniority_ts: now, + last_heartbeat: now, + inserted_at: now, update_reason: 0, }, verified_radio_threshold: coverage_point_calculator::RadioThreshold::Verified, @@ -2077,13 +2077,13 @@ mod test { upload_speed: coverage_point_calculator::BytesPs::new(100_000_000), download_speed: coverage_point_calculator::BytesPs::new(100_000_000), latency_millis: 10, - timestamp: now.clone(), + timestamp: now, }, coverage_point_calculator::Speedtest { upload_speed: coverage_point_calculator::BytesPs::new(100_000_000), download_speed: coverage_point_calculator::BytesPs::new(100_000_000), latency_millis: 10, - timestamp: now.clone(), + timestamp: now, }, ], }, @@ -2099,9 +2099,9 @@ mod test { }], seniority: Seniority { uuid: Uuid::new_v4(), - seniority_ts: now.clone(), - last_heartbeat: now.clone(), - inserted_at: now.clone(), + seniority_ts: now, + last_heartbeat: now, + inserted_at: now, update_reason: 0, }, verified_radio_threshold: coverage_point_calculator::RadioThreshold::Verified, From 17b800968a111940374d78ae3e3dfc929c07245f Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 11:40:15 -0700 Subject: [PATCH 28/40] remove need for cloning radio_info we can clone the speed_tests and trust_scores as they enter the calculator, but everything else can work with a reference for much longer. --- mobile_verifier/src/reward_shares.rs | 45 +++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index d029d3ca0..2ac5517f5 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -560,12 +560,8 @@ impl CoveragePoints { async fn coverage_points( &self, radio_id: &RadioId, - radio_info: RadioInfo, ) -> anyhow::Result { - let radio_type = radio_info.radio_type; - let trust_scores = radio_info.trust_scores; - let verified = radio_info.verified_radio_threshold; - let speedtests = radio_info.speedtests; + let radio_info = self.radio_infos.get(radio_id).unwrap(); let hexes = { let this = &self; @@ -578,26 +574,15 @@ impl CoveragePoints { }; let coverage_points = coverage_point_calculator::CoveragePoints::new( - radio_type, - verified, - speedtests, - trust_scores, + radio_info.radio_type, + radio_info.verified_radio_threshold, + radio_info.speedtests.clone(), + radio_info.trust_scores.clone(), hexes, )?; Ok(coverage_points) } - // =============================================================== - - /// Only used for testing - pub async fn test_hotspot_reward_shares(&self, hotspot: &RadioId) -> Decimal { - let radio = self.radio_infos.get(hotspot).unwrap(); - - self.coverage_points(hotspot, radio.clone()) - .await - .expect("coverage points for hotspot") - .reward_shares - } pub async fn into_rewards( self, @@ -607,12 +592,16 @@ impl CoveragePoints { let mut total_shares: Decimal = dec!(0); let mut processed_radios = vec![]; - for (id, radio_info) in self.radio_infos.iter() { - let points_res = self.coverage_points(id, radio_info.clone()).await; + for (radio_id, radio_info) in self.radio_infos.iter() { + let points_res = self.coverage_points(radio_id).await; let points = match points_res { Ok(points) => points, Err(err) => { - tracing::error!(pubkey = id.0.to_string(), ?err, "could not reward radio"); + tracing::error!( + pubkey = radio_id.0.to_string(), + ?err, + "could not reward radio" + ); continue; } }; @@ -621,7 +610,7 @@ impl CoveragePoints { let coverage_object_uuid = radio_info.coverage_obj_uuid; total_shares += points.reward_shares; - processed_radios.push((id.clone(), points, seniority, coverage_object_uuid)); + processed_radios.push((radio_id.clone(), points, seniority, coverage_object_uuid)); } let Some(rewards_per_share) = available_poc_rewards.checked_div(total_shares) else { @@ -649,6 +638,14 @@ impl CoveragePoints { .filter(|(poc_reward, _mobile_reward)| *poc_reward > 0), ) } + + /// Only used for testing + pub async fn test_hotspot_reward_shares(&self, hotspot: &RadioId) -> Decimal { + self.coverage_points(hotspot) + .await + .expect("coverage points for hotspot") + .reward_shares + } } pub fn get_total_scheduled_tokens(duration: Duration) -> Decimal { From 88d7a3e8fe81ae2466d5b8584a67882619084795 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 12:27:20 -0700 Subject: [PATCH 29/40] Remove replaced coverage point code --- mobile_verifier/src/coverage.rs | 886 +-------------------------- mobile_verifier/src/reward_shares.rs | 163 +---- 2 files changed, 4 insertions(+), 1045 deletions(-) diff --git a/mobile_verifier/src/coverage.rs b/mobile_verifier/src/coverage.rs index 94cb1dbb6..1b47810ea 100644 --- a/mobile_verifier/src/coverage.rs +++ b/mobile_verifier/src/coverage.rs @@ -17,25 +17,17 @@ use futures::{ TryFutureExt, TryStreamExt, }; use h3o::{CellIndex, LatLng}; -use helium_crypto::PublicKeyBinary; use helium_proto::services::{ mobile_config::NetworkKeyRole, poc_mobile::{self as proto, CoverageObjectValidity, SignalLevel as SignalLevelProto}, }; use hex_assignments::assignment::HexAssignments; use hextree::Cell; -use mobile_config::{ - boosted_hex_info::{BoostedHex, BoostedHexes}, - client::AuthorizationClient, -}; +use mobile_config::client::AuthorizationClient; use retainer::{entry::CacheReadGuard, Cache}; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; + use sqlx::{FromRow, PgPool, Pool, Postgres, QueryBuilder, Transaction, Type}; use std::{ - cmp::Ordering, - collections::{BTreeMap, BinaryHeap, HashMap}, - num::NonZeroU32, pin::pin, sync::Arc, time::{Duration, Instant}, @@ -384,132 +376,6 @@ pub struct HexCoverage { pub assignments: HexAssignments, } -#[derive(Eq, Debug, Clone)] -pub struct IndoorCoverageLevel { - radio_key: OwnedKeyType, - seniority_timestamp: DateTime, - pub hotspot: PublicKeyBinary, - signal_level: SignalLevel, - hex_assignments: HexAssignments, -} - -impl PartialEq for IndoorCoverageLevel { - fn eq(&self, other: &Self) -> bool { - self.seniority_timestamp == other.seniority_timestamp - } -} - -impl PartialOrd for IndoorCoverageLevel { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for IndoorCoverageLevel { - fn cmp(&self, other: &Self) -> Ordering { - self.seniority_timestamp.cmp(&other.seniority_timestamp) - } -} - -impl IndoorCoverageLevel { - fn coverage_points(&self) -> Decimal { - match (&self.radio_key, self.signal_level) { - (OwnedKeyType::Wifi(_), SignalLevel::High) => dec!(400), - (OwnedKeyType::Wifi(_), SignalLevel::Low) => dec!(100), - (OwnedKeyType::Cbrs(_), SignalLevel::High) => dec!(100), - (OwnedKeyType::Cbrs(_), SignalLevel::Low) => dec!(25), - _ => dec!(0), - } - } -} - -#[derive(Eq, Debug, Clone)] -pub struct OutdoorCoverageLevel { - radio_key: OwnedKeyType, - seniority_timestamp: DateTime, - pub hotspot: PublicKeyBinary, - signal_power: i32, - signal_level: SignalLevel, - hex_assignments: HexAssignments, -} - -impl PartialEq for OutdoorCoverageLevel { - fn eq(&self, other: &Self) -> bool { - self.signal_power == other.signal_power - && self.seniority_timestamp == other.seniority_timestamp - } -} - -impl PartialOrd for OutdoorCoverageLevel { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for OutdoorCoverageLevel { - fn cmp(&self, other: &Self) -> Ordering { - self.signal_power - .cmp(&other.signal_power) - .reverse() - .then_with(|| self.seniority_timestamp.cmp(&other.seniority_timestamp)) - } -} - -impl OutdoorCoverageLevel { - fn coverage_points(&self) -> Decimal { - match (&self.radio_key, self.signal_level) { - (OwnedKeyType::Wifi(_), SignalLevel::High) => dec!(16), - (OwnedKeyType::Wifi(_), SignalLevel::Medium) => dec!(8), - (OwnedKeyType::Wifi(_), SignalLevel::Low) => dec!(4), - (OwnedKeyType::Wifi(_), SignalLevel::None) => dec!(0), - (OwnedKeyType::Cbrs(_), SignalLevel::High) => dec!(4), - (OwnedKeyType::Cbrs(_), SignalLevel::Medium) => dec!(2), - (OwnedKeyType::Cbrs(_), SignalLevel::Low) => dec!(1), - (OwnedKeyType::Cbrs(_), SignalLevel::None) => dec!(0), - } - } -} - -#[derive(PartialEq, Debug)] -pub struct CoverageReward { - pub radio_key: OwnedKeyType, - pub points: CoverageRewardPoints, - pub hotspot: PublicKeyBinary, - pub boosted_hex_info: BoostedHex, -} - -impl CoverageReward { - fn has_rewards(&self) -> bool { - self.points.points() > Decimal::ZERO - } -} - -#[derive(PartialEq, Debug)] -pub struct CoverageRewardPoints { - pub boost_multiplier: NonZeroU32, - pub coverage_points: Decimal, - pub hex_assignments: HexAssignments, - pub rank: Option, -} - -impl CoverageRewardPoints { - pub fn points(&self) -> Decimal { - let oracle_multiplier = if self.boost_multiplier.get() > 1 { - dec!(1.0) - } else { - self.hex_assignments.boosting_multiplier() - }; - - let points = self.coverage_points * oracle_multiplier; - - if let Some(rank) = self.rank { - points * rank - } else { - points - } - } -} - #[async_trait::async_trait] pub trait CoveredHexStream { async fn covered_hex_stream<'a>( @@ -633,185 +499,6 @@ pub async fn clear_coverage_objects( Ok(()) } -type IndoorCellTree = HashMap>>; -type OutdoorCellTree = HashMap>; - -#[derive(Default, Debug)] -pub struct CoveredHexes { - pub indoor_cbrs: IndoorCellTree, - pub indoor_wifi: IndoorCellTree, - pub outdoor_cbrs: OutdoorCellTree, - pub outdoor_wifi: OutdoorCellTree, -} - -pub const MAX_INDOOR_RADIOS_PER_RES12_HEX: usize = 1; -pub const MAX_OUTDOOR_RADIOS_PER_RES12_HEX: usize = 3; -pub const OUTDOOR_REWARD_WEIGHTS: [Decimal; 3] = [dec!(1.0), dec!(0.50), dec!(0.25)]; - -impl CoveredHexes { - /// Aggregate the coverage. Returns whether or not any of the hexes are boosted - pub async fn aggregate_coverage( - &mut self, - hotspot: &PublicKeyBinary, - boosted_hexes: &BoostedHexes, - covered_hexes: impl Stream>, - ) -> Result { - let mut covered_hexes = std::pin::pin!(covered_hexes); - let mut boosted = false; - - while let Some(hex_coverage) = covered_hexes.next().await.transpose()? { - boosted |= boosted_hexes.is_boosted(&hex_coverage.hex); - match (hex_coverage.indoor, &hex_coverage.radio_key) { - (true, OwnedKeyType::Cbrs(_)) => { - insert_indoor_coverage(&mut self.indoor_cbrs, hotspot, hex_coverage); - } - (true, OwnedKeyType::Wifi(_)) => { - insert_indoor_coverage(&mut self.indoor_wifi, hotspot, hex_coverage); - } - (false, OwnedKeyType::Cbrs(_)) => { - insert_outdoor_coverage(&mut self.outdoor_cbrs, hotspot, hex_coverage); - } - (false, OwnedKeyType::Wifi(_)) => { - insert_outdoor_coverage(&mut self.outdoor_wifi, hotspot, hex_coverage); - } - } - } - - Ok(boosted) - } - - /// Returns the radios that should be rewarded for giving coverage. - pub fn into_coverage_rewards( - self, - boosted_hexes: &BoostedHexes, - epoch_start: DateTime, - ) -> impl Iterator + '_ { - let outdoor_cbrs_rewards = - into_outdoor_rewards(self.outdoor_cbrs, boosted_hexes, epoch_start); - - let outdoor_wifi_rewards = - into_outdoor_rewards(self.outdoor_wifi, boosted_hexes, epoch_start); - - let indoor_cbrs_rewards = into_indoor_rewards(self.indoor_cbrs, boosted_hexes, epoch_start); - let indoor_wifi_rewards = into_indoor_rewards(self.indoor_wifi, boosted_hexes, epoch_start); - - outdoor_cbrs_rewards - .chain(outdoor_wifi_rewards) - .chain(indoor_cbrs_rewards) - .chain(indoor_wifi_rewards) - .filter(CoverageReward::has_rewards) - } -} - -fn insert_indoor_coverage( - indoor: &mut IndoorCellTree, - hotspot: &PublicKeyBinary, - hex_coverage: HexCoverage, -) { - indoor - .entry(hex_coverage.hex) - .or_default() - .entry(hex_coverage.signal_level) - .or_default() - .push(IndoorCoverageLevel { - radio_key: hex_coverage.radio_key, - seniority_timestamp: hex_coverage.coverage_claim_time, - signal_level: hex_coverage.signal_level, - hotspot: hotspot.clone(), - hex_assignments: hex_coverage.assignments, - }) -} - -fn insert_outdoor_coverage( - outdoor: &mut OutdoorCellTree, - hotspot: &PublicKeyBinary, - hex_coverage: HexCoverage, -) { - outdoor - .entry(hex_coverage.hex) - .or_default() - .push(OutdoorCoverageLevel { - radio_key: hex_coverage.radio_key, - seniority_timestamp: hex_coverage.coverage_claim_time, - signal_level: hex_coverage.signal_level, - signal_power: hex_coverage.signal_power, - hotspot: hotspot.clone(), - hex_assignments: hex_coverage.assignments, - }); -} - -fn into_outdoor_rewards( - outdoor: OutdoorCellTree, - boosted_hexes: &BoostedHexes, - epoch_start: DateTime, -) -> impl Iterator + '_ { - outdoor.into_iter().flat_map(move |(hex, radios)| { - radios - .into_sorted_vec() - .into_iter() - .take(MAX_OUTDOOR_RADIOS_PER_RES12_HEX) - .zip(OUTDOOR_REWARD_WEIGHTS) - .map(move |(cl, rank)| { - let boost_multiplier = boosted_hexes - .get_current_multiplier(hex, epoch_start) - .unwrap_or(NonZeroU32::new(1).unwrap()); - - CoverageReward { - points: CoverageRewardPoints { - boost_multiplier, - coverage_points: cl.coverage_points(), - hex_assignments: cl.hex_assignments, - rank: Some(rank), - }, - hotspot: cl.hotspot, - radio_key: cl.radio_key, - boosted_hex_info: BoostedHex { - location: hex, - multiplier: boost_multiplier, - }, - } - }) - }) -} - -fn into_indoor_rewards( - indoor: IndoorCellTree, - boosted_hexes: &BoostedHexes, - epoch_start: DateTime, -) -> impl Iterator + '_ { - indoor - .into_iter() - .flat_map(move |(hex, mut radios)| { - radios.pop_last().map(move |(_, radios)| { - radios - .into_sorted_vec() - .into_iter() - .take(MAX_INDOOR_RADIOS_PER_RES12_HEX) - .map(move |cl| { - let boost_multiplier = boosted_hexes - .get_current_multiplier(hex, epoch_start) - .unwrap_or(NonZeroU32::new(1).unwrap()); - - CoverageReward { - points: CoverageRewardPoints { - boost_multiplier, - coverage_points: cl.coverage_points(), - hex_assignments: cl.hex_assignments, - rank: None, - }, - hotspot: cl.hotspot, - radio_key: cl.radio_key, - boosted_hex_info: BoostedHex { - location: hex, - multiplier: boost_multiplier, - }, - } - }) - }) - }) - .flatten() -} - type CoverageClaimTimeKey = ((String, HbType), Option); pub struct CoverageClaimTimeCache { @@ -966,572 +653,3 @@ impl CachedCoverageObject<'_> { }) } } - -#[cfg(test)] -mod test { - use std::str::FromStr; - - use super::*; - use chrono::NaiveDate; - use futures::stream::iter; - use hex_assignments::Assignment; - use hextree::Cell; - - fn hex_assignments_mock() -> HexAssignments { - HexAssignments { - footfall: Assignment::A, - urbanized: Assignment::A, - landtype: Assignment::A, - } - } - - /// Test to ensure that if there are multiple radios with different signal levels - /// in a given hex, that the one with the highest signal level is chosen. - #[tokio::test] - async fn ensure_max_signal_level_selected() { - let owner: PublicKeyBinary = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6" - .parse() - .expect("failed owner parse"); - let mut covered_hexes = CoveredHexes::default(); - covered_hexes - .aggregate_coverage( - &owner, - &BoostedHexes::default(), - iter(vec![ - anyhow::Ok(indoor_cbrs_hex_coverage("1", SignalLevel::None, None)), - anyhow::Ok(indoor_cbrs_hex_coverage("2", SignalLevel::Low, None)), - anyhow::Ok(indoor_cbrs_hex_coverage("3", SignalLevel::High, None)), - anyhow::Ok(indoor_cbrs_hex_coverage("4", SignalLevel::Low, None)), - anyhow::Ok(indoor_cbrs_hex_coverage("5", SignalLevel::None, None)), - ]), - ) - .await - .unwrap(); - let rewards: Vec<_> = covered_hexes - .into_coverage_rewards(&BoostedHexes::default(), Utc::now()) - .collect(); - assert_eq!( - rewards, - vec![CoverageReward { - radio_key: OwnedKeyType::Cbrs("3".to_string()), - hotspot: owner, - points: CoverageRewardPoints { - coverage_points: dec!(100), - boost_multiplier: NonZeroU32::new(1).unwrap(), - hex_assignments: hex_assignments_mock(), - rank: None - }, - boosted_hex_info: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), - }, - }] - ); - } - - fn date(year: i32, month: u32, day: u32) -> DateTime { - NaiveDate::from_ymd_opt(year, month, day) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - .and_utc() - } - - fn indoor_hex_coverage_with_date( - cbsd_id: &str, - signal_level: SignalLevel, - coverage_claim_time: DateTime, - ) -> HexCoverage { - HexCoverage { - uuid: Uuid::new_v4(), - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: true, - radio_key: OwnedKeyType::Cbrs(cbsd_id.to_string()), - signal_level, - // Signal power is ignored for indoor radios: - signal_power: 0, - coverage_claim_time, - inserted_at: DateTime::::MIN_UTC, - assignments: hex_assignments_mock(), - } - } - - /// Test to ensure that if there are more than five radios with the highest signal - /// level in a given hex, that the five oldest radios are chosen. - #[tokio::test] - async fn ensure_oldest_radio_selected() { - let owner: PublicKeyBinary = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6" - .parse() - .expect("failed owner parse"); - let mut covered_hexes = CoveredHexes::default(); - covered_hexes - .aggregate_coverage( - &owner, - &BoostedHexes::default(), - iter(vec![ - anyhow::Ok(indoor_hex_coverage_with_date( - "1", - SignalLevel::High, - date(1980, 1, 1), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "2", - SignalLevel::High, - date(1970, 1, 5), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "3", - SignalLevel::High, - date(1990, 2, 2), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "4", - SignalLevel::High, - date(1970, 1, 4), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "5", - SignalLevel::High, - date(1975, 3, 3), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "6", - SignalLevel::High, - date(1970, 1, 3), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "7", - SignalLevel::High, - date(1974, 2, 2), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "8", - SignalLevel::High, - date(1970, 1, 2), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "9", - SignalLevel::High, - date(1976, 5, 2), - )), - anyhow::Ok(indoor_hex_coverage_with_date( - "10", - SignalLevel::High, - date(1970, 1, 1), - )), - ]), - ) - .await - .unwrap(); - let rewards: Vec<_> = covered_hexes - .into_coverage_rewards(&BoostedHexes::default(), Utc::now()) - .collect(); - assert_eq!( - rewards, - vec![CoverageReward { - radio_key: OwnedKeyType::Cbrs("10".to_string()), - hotspot: owner.clone(), - points: CoverageRewardPoints { - coverage_points: dec!(100), - boost_multiplier: NonZeroU32::new(1).unwrap(), - hex_assignments: hex_assignments_mock(), - rank: None - }, - boosted_hex_info: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), - }, - }] - ); - } - - #[tokio::test] - async fn ensure_outdoor_radios_ranked_by_power() { - let owner: PublicKeyBinary = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6" - .parse() - .expect("failed owner parse"); - let mut covered_hexes = CoveredHexes::default(); - covered_hexes - .aggregate_coverage( - &owner, - &BoostedHexes::default(), - iter(vec![ - anyhow::Ok(outdoor_cbrs_hex_coverage("1", -946, date(2022, 8, 1))), - anyhow::Ok(outdoor_cbrs_hex_coverage("2", -936, date(2022, 12, 5))), - anyhow::Ok(outdoor_cbrs_hex_coverage("3", -887, date(2022, 12, 2))), - anyhow::Ok(outdoor_cbrs_hex_coverage("4", -887, date(2022, 12, 1))), - anyhow::Ok(outdoor_cbrs_hex_coverage("5", -773, date(2023, 5, 1))), - ]), - ) - .await - .unwrap(); - let rewards: Vec<_> = covered_hexes - .into_coverage_rewards(&BoostedHexes::default(), Utc::now()) - .collect(); - assert_eq!( - rewards, - vec![ - CoverageReward { - radio_key: OwnedKeyType::Cbrs("5".to_string()), - hotspot: owner.clone(), - points: CoverageRewardPoints { - coverage_points: dec!(4), - rank: Some(dec!(1.0)), - boost_multiplier: NonZeroU32::new(1).unwrap(), - hex_assignments: hex_assignments_mock(), - }, - boosted_hex_info: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), - }, - }, - CoverageReward { - radio_key: OwnedKeyType::Cbrs("4".to_string()), - hotspot: owner.clone(), - points: CoverageRewardPoints { - coverage_points: dec!(4), - rank: Some(dec!(0.50)), - boost_multiplier: NonZeroU32::new(1).unwrap(), - hex_assignments: hex_assignments_mock(), - }, - boosted_hex_info: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), - }, - }, - CoverageReward { - radio_key: OwnedKeyType::Cbrs("3".to_string()), - hotspot: owner, - points: CoverageRewardPoints { - coverage_points: dec!(4), - rank: Some(dec!(0.25)), - boost_multiplier: NonZeroU32::new(1).unwrap(), - hex_assignments: hex_assignments_mock(), - }, - boosted_hex_info: BoostedHex { - location: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - multiplier: NonZeroU32::new(1).unwrap(), - }, - } - ] - ); - } - - #[tokio::test] - async fn hip_105_ensure_all_types_get_rewards() -> anyhow::Result<()> { - let mut covered_hexes = CoveredHexes::default(); - let boosted_hexes = BoostedHexes::default(); - - let outdoor_cbrs_owner1 = - PublicKeyBinary::from_str("11eX55faMbqZB7jzN4p67m6w7ScPMH6ubnvCjCPLh72J49PaJEL")?; - covered_hexes - .aggregate_coverage( - &outdoor_cbrs_owner1, - &boosted_hexes, - iter(vec![ - anyhow::Ok(outdoor_cbrs_hex_coverage("oco1-1", -700, date(2024, 2, 20))), - anyhow::Ok(outdoor_cbrs_hex_coverage("oco1-2", -700, date(2024, 2, 21))), - anyhow::Ok(outdoor_cbrs_hex_coverage("oco1-3", -699, date(2024, 2, 23))), - anyhow::Ok(outdoor_cbrs_hex_coverage("oco1-4", -700, date(2024, 2, 19))), - ]), - ) - .await?; - - let indoor_cbrs_owner1 = - PublicKeyBinary::from_str("11PfLUsMAfsozjy2kcERF43UuhNAhicEQM8ioutFM322Eu37D4m")?; - covered_hexes - .aggregate_coverage( - &indoor_cbrs_owner1, - &boosted_hexes, - iter(vec![ - anyhow::Ok(indoor_cbrs_hex_coverage( - "ico1-1", - SignalLevel::High, - Some(date(2024, 2, 20)), - )), - anyhow::Ok(indoor_cbrs_hex_coverage( - "ico1-2", - SignalLevel::High, - Some(date(2024, 2, 21)), - )), - ]), - ) - .await?; - - let outdoor_wifi_owner1 = - PublicKeyBinary::from_str("1trSuseczBVbfpbJjefoFsuPazRSrzJjCXaKJPU9B3HKJvb6sdqTepcbY2zWBq2yMTt7Jsf7NZCm28ez856kDa5MT3Ja1gh8HyZWS8k9LCSFSWiDNG2YmcCpLgnhGrEw9FriCVPLuXaciQ2Fu9ztW7r1U1Pv64i3HvpkC4mmQWE9DSq7tgiNkNhNuWBA3Sf8KbtefMPofTxjCsfVCUKX2ow8MScn82CK6vWUZNUPonpTJydKVLNiMGfvceY1MsfXtHdx6bUCjoFoZNkikAzEpgArczJV9CdhkBjKX3xLVLpehdrBDGu8aBLfRbNJ4RRz9Gj4pHFnBhFq78tRGi1USpnf6Dohp9bA18qr4XdPJc59Qz")?; - covered_hexes - .aggregate_coverage( - &outdoor_wifi_owner1, - &boosted_hexes, - iter(vec![anyhow::Ok(outdoor_wifi_hex_coverage( - &outdoor_wifi_owner1, - -700, - date(2024, 2, 20), - ))]), - ) - .await?; - - let outdoor_wifi_owner2 = - PublicKeyBinary::from_str("1trSuseerjmxKaD43hSLPD1oWTt6Y6svqJX7WJecMwKKcRk1355AQp1GSkUNV7fnL9QYGpZSS378XoxmaHte5PCD64NYzJ1x7bBNdq6qBRFRDqTW1PGPMatjX3i18Y39hh8Ngsephg93YCZoVbvfGc5YMmtvxqqP4WXy4UxmiTZ6uuYzPV5U31piAFVxaUhTZtoQLCyLzAZEks8bj2cP6EyEFMecHb9Vq76d4qnXdjARvFim7xACkBKHTnAwEEN8wfWfGEw5QBQMfpvvSLUerL64xR72tT3SrM7qUXk9m7fTbLuwg8XfUKEs2iqhPSfSu2v4DpcKY7L4fvu8BT2WsMChC3xaPWWiibTVatoNLNxTH6")?; - covered_hexes - .aggregate_coverage( - &outdoor_wifi_owner2, - &boosted_hexes, - iter(vec![anyhow::Ok(outdoor_wifi_hex_coverage( - &outdoor_wifi_owner2, - -700, - date(2024, 2, 21), - ))]), - ) - .await?; - - let outdoor_wifi_owner3 = - PublicKeyBinary::from_str("1trSusefexd9C3purVPScg433RXrVb5kU9hLTTKjuc2dTju9udy2rsgAYUTjhxARa9ewZAW1PdsjsErGyaHNJKNDkjHfzuHZmPm7vWK3A13sckxRbwSBtBXAMg4nyChmoJ5JgZVeeHBtdYX69emPoDD8niKSx5vkkoBw5g1AYS7S4LfnpGhCtwKA8PzjjzE3ZY6dWQjm2oCut31ScsH9nZfBHriHpkTbNK8KttkFRU3ax3wdJXmN996PPbYsgm1wx8ctU9iU6Q7FvTvVkZTGfHHcH8J38YCuWB9LfizRSueKWNSPbfsrJgQe3otTYtdU7NDWWQzrpv3yATS2NJzyorKpjg8hH5J2krtU3KdByBgZdU")?; - covered_hexes - .aggregate_coverage( - &outdoor_wifi_owner3, - &boosted_hexes, - iter(vec![anyhow::Ok(outdoor_wifi_hex_coverage( - &outdoor_wifi_owner3, - -699, - date(2024, 2, 23), - ))]), - ) - .await?; - - let outdoor_wifi_owner4 = - PublicKeyBinary::from_str("1trSusf5rnmrUHqyv28ksYJGBBkHZi821ss7vLkBchUPi6vxDHpHGoscCftuHddxpaHgMacxD7fyHESf8Ht3JpRjebZnTzZMwqs6u6z2v8S7VZzxjv5KkaNZpX2CPYGYNfVRWC2tovSaUwEdc3P6Tyk6S9axAw7WM9pP2sxyEqWiCmyzhCnnd8xhZqaTKtiyoamvVTXqB1iZaUFX2KtSB6pLVGrDCUxGs7x4PrMrgAcPcdDF1jrF6s7EpAR9MjRHv6qxstoSHnGMpTeZaXLJEhySqtnSvyQEJaT218zuDSoHArKRUSQ9ViWE55T8hbwsVDusNDdayS4JG2fRMoDkj8LPYHvhMtVzQUDSg1ufFEFukh")?; - covered_hexes - .aggregate_coverage( - &outdoor_wifi_owner4, - &boosted_hexes, - iter(vec![anyhow::Ok(outdoor_wifi_hex_coverage( - &outdoor_wifi_owner4, - -700, - date(2024, 2, 19), - ))]), - ) - .await?; - - let indoor_wifi_owner1 = - PublicKeyBinary::from_str("1trSusf79ALaHuYSxUcvHLQEtHUgTnc25rfjyUUSKDs9AUwvXkJ7CQoGMrY7RhVpXyKYH4HaDnekRLYTUq5pczPk4XqnzXnACrwbY5CzhzdTSQkHRN2LuHvgQeySeh4LvjfhRP3Cru89zTGNNGMDXkpASuz3NkQx3ctqnTcdrjLgcBavQQmASofARxrqSPUz4UFTU1Gp4eRdaJgu1G7ys1f8NsjH5WU6bi5N4U5cWRVQkC7FEJZsGFn1sNferANVwkkSR2NLEpwYvL5qpGTYtk7zcqPrHY5hNC6jkjWhM5S4JPDYzZNcxRW28ekRCp2igJCqErA3APbcwkaZPXUxpqFJGWqu6GJf7aZRKz8R9cNAmd")?; - covered_hexes - .aggregate_coverage( - &indoor_wifi_owner1, - &boosted_hexes, - iter(vec![anyhow::Ok(indoor_wifi_hex_coverage( - &indoor_wifi_owner1, - SignalLevel::High, - date(2024, 2, 19), - ))]), - ) - .await?; - - let indoor_wifi_owner2 = - PublicKeyBinary::from_str("1trSusew4P9SVD2q9GjvNYf8e5qEH2NmZRQDnkXodQt1fmpcR9cG28iA3LJD476H31wrNcr6jbfBhoPdyJvfepQonxXH7kUDjpMcVJqfMZGeQyXQvaxxnPh4yzoPonpS5RM6VSmsk4WNczr6nBUa49ak1XM8s5DCGSRZEPqWhXfG9urQ8hgDSYdkd61eBcWmThRKLAfarT5cE1ZaeexFtUgjgRBUGd7ifCtchZTgkWTa9WVsMdpWTjFd8GUkaTekX1RWzFDtFyETGZHbD6wDut729EfoBuSKowJuwFv2LYZr7Cw4qKPmVboDBpem1ZramSq3PmatdrpNHHipXniz4Z1vtM1vfgtJ57o8BrWSQNuD9B")?; - covered_hexes - .aggregate_coverage( - &indoor_wifi_owner2, - &boosted_hexes, - iter(vec![anyhow::Ok(indoor_wifi_hex_coverage( - &indoor_wifi_owner2, - SignalLevel::High, - date(2024, 2, 20), - ))]), - ) - .await?; - - //Calculate coverage points - let rewards: Vec<_> = covered_hexes - .into_coverage_rewards(&boosted_hexes, Utc::now()) - .collect(); - - // assert outdoor cbrs radios - assert_eq!( - dec!(4), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("oco1-3".to_string())) - .unwrap() - .points - .points() - ); - - assert_eq!( - dec!(2), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("oco1-4".to_string())) - .unwrap() - .points - .points() - ); - - assert_eq!( - dec!(1), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("oco1-1".to_string())) - .unwrap() - .points - .points() - ); - - assert_eq!( - None, - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("oco1-2".to_string())) - ); - - // assert indoor cbrs radios - assert_eq!( - dec!(100), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("ico1-1".to_string())) - .unwrap() - .points - .points() - ); - - assert_eq!( - None, - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Cbrs("ico1-2".to_string())) - ); - - //assert outdoor wifi radios - assert_eq!( - dec!(16), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(outdoor_wifi_owner3.clone())) - .unwrap() - .points - .points() - ); - - assert_eq!( - dec!(8), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(outdoor_wifi_owner4.clone())) - .unwrap() - .points - .points() - ); - - assert_eq!( - dec!(4), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(outdoor_wifi_owner1.clone())) - .unwrap() - .points - .points() - ); - - assert_eq!( - None, - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(outdoor_wifi_owner2.clone())) - ); - - //assert indoor wifi radios - assert_eq!( - dec!(400), - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(indoor_wifi_owner1.clone())) - .unwrap() - .points - .points() - ); - - assert_eq!( - None, - rewards - .iter() - .find(|r| r.radio_key == OwnedKeyType::Wifi(indoor_wifi_owner2.clone())) - ); - - Ok(()) - } - - fn indoor_cbrs_hex_coverage( - cbsd_id: &str, - signal_level: SignalLevel, - coverage_claim_time: Option>, - ) -> HexCoverage { - HexCoverage { - uuid: Uuid::new_v4(), - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: true, - radio_key: OwnedKeyType::Cbrs(cbsd_id.to_string()), - signal_level, - // Signal power is ignored for indoor radios: - signal_power: 0, - coverage_claim_time: coverage_claim_time.unwrap_or(DateTime::::MIN_UTC), - inserted_at: DateTime::::MIN_UTC, - assignments: hex_assignments_mock(), - } - } - - fn outdoor_cbrs_hex_coverage( - cbsd_id: &str, - signal_power: i32, - coverage_claim_time: DateTime, - ) -> HexCoverage { - HexCoverage { - uuid: Uuid::new_v4(), - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: false, - radio_key: OwnedKeyType::Cbrs(cbsd_id.to_string()), - signal_power, - signal_level: SignalLevel::High, - coverage_claim_time, - inserted_at: DateTime::::MIN_UTC, - assignments: hex_assignments_mock(), - } - } - - fn outdoor_wifi_hex_coverage( - hotspot_key: &PublicKeyBinary, - signal_power: i32, - coverage_claim_time: DateTime, - ) -> HexCoverage { - HexCoverage { - uuid: Uuid::new_v4(), - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: false, - radio_key: OwnedKeyType::Wifi(hotspot_key.clone()), - signal_power, - signal_level: SignalLevel::High, - coverage_claim_time, - inserted_at: DateTime::::MIN_UTC, - assignments: hex_assignments_mock(), - } - } - - fn indoor_wifi_hex_coverage( - hotspot_key: &PublicKeyBinary, - signal_level: SignalLevel, - coverage_claim_time: DateTime, - ) -> HexCoverage { - HexCoverage { - uuid: Uuid::new_v4(), - hex: Cell::from_raw(0x8a1fb46622dffff).expect("valid h3 cell"), - indoor: true, - radio_key: OwnedKeyType::Wifi(hotspot_key.clone()), - signal_power: 0, - signal_level, - coverage_claim_time, - inserted_at: DateTime::::MIN_UTC, - assignments: hex_assignments_mock(), - } - } -} diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 2ac5517f5..02cc41a07 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -671,15 +671,13 @@ pub fn get_scheduled_tokens_for_oracles(duration: Duration) -> Decimal { #[cfg(test)] mod test { - #![allow(unused)] use super::*; use hex_assignments::{assignment::HexAssignments, Assignment}; - use mobile_config::boosted_hex_info::BoostedHex; use crate::{ cell_type::CellType, - coverage::{CoverageRewardPoints, CoveredHexStream, HexCoverage, Seniority}, + coverage::{CoveredHexStream, HexCoverage, Seniority}, data_session::{self, HotspotDataSession, HotspotReward}, heartbeats::{HeartbeatReward, KeyType, OwnedKeyType}, reward_shares, @@ -695,118 +693,9 @@ mod test { }; use hextree::Cell; use prost::Message; - use std::{collections::HashMap, num::NonZeroU32}; + use std::collections::HashMap; use uuid::Uuid; - #[derive(Debug)] - struct CoverageRewardPointsWithMultiplier { - coverage_points: CoverageRewardPoints, - boosted_hex: BoostedHex, - } - - #[derive(Debug)] - struct RadioPoints { - location_trust_score_multiplier: Decimal, - coverage_object: Uuid, - seniority: DateTime, - // Boosted hexes are included in CoverageRewardPointsWithMultiplier, - // this gets included in the radio reward share proto - reward_points: Vec, - } - - impl RadioPoints { - fn new( - location_trust_score_multiplier: Decimal, - coverage_object: Uuid, - seniority: DateTime, - ) -> Self { - Self { - location_trust_score_multiplier, - seniority, - coverage_object, - reward_points: vec![], - } - } - - fn hex_points(&self) -> Decimal { - self.reward_points - .iter() - .map(|c| c.coverage_points.points() * Decimal::from(c.boosted_hex.multiplier.get())) - .sum::() - } - - fn coverage_points(&self) -> Decimal { - let coverage_points = self.hex_points(); - (self.location_trust_score_multiplier * coverage_points).max(Decimal::ZERO) - } - - fn poc_reward(&self, reward_per_share: Decimal, speedtest_multiplier: Decimal) -> Decimal { - reward_per_share * speedtest_multiplier * self.coverage_points() - } - } - - // pub type HotspotBoostedHexes = HashMap; - - #[derive(Debug, Default)] - struct HotspotPoints { - /// Points are multiplied by the multiplier to get shares. - /// Multiplier should never be zero. - speedtest_multiplier: Decimal, - radio_points: HashMap, RadioPoints>, - } - - impl HotspotPoints { - pub fn add_coverage_entry( - &mut self, - radio_key: OwnedKeyType, - hotspot: PublicKeyBinary, - points: CoverageRewardPoints, - boosted_hex_info: BoostedHex, - verified_radio_thresholds: &VerifiedRadioThresholds, - ) { - let cbsd_id = radio_key.clone().into_cbsd_id(); - let rp = self.radio_points.get_mut(&cbsd_id).unwrap(); - // need to consider requirements from hip93 & hip84 before applying any boost - // hip93: if radio is wifi & location_trust score multiplier < 0.75, no boosting - // hip84: if radio has not met minimum data and subscriber thresholds, no boosting - let final_boost_info = if radio_key.is_wifi() - && rp.location_trust_score_multiplier < dec!(0.75) - || !verified_radio_thresholds.is_verified(hotspot, cbsd_id) - { - BoostedHex { - location: boosted_hex_info.location, - multiplier: NonZeroU32::new(1).unwrap(), - } - } else { - boosted_hex_info - }; - - rp.reward_points.push(CoverageRewardPointsWithMultiplier { - coverage_points: points, - boosted_hex: final_boost_info, - }); - } - } - - impl HotspotPoints { - pub fn new(speedtest_multiplier: Decimal) -> Self { - Self { - speedtest_multiplier, - radio_points: HashMap::new(), - } - } - } - - impl HotspotPoints { - pub fn total_points(&self) -> Decimal { - self.speedtest_multiplier - * self - .radio_points - .values() - .fold(Decimal::ZERO, |sum, radio| sum + radio.coverage_points()) - } - } - fn hex_assignments_mock() -> HexAssignments { HexAssignments { footfall: Assignment::A, @@ -1970,56 +1859,8 @@ mod test { .parse() .expect("failed gw2 parse"); - let c1 = "P27-SCE4255W2107CW5000014".to_string(); - let c2 = "P27-SCE4255W2107CW5000015".to_string(); - let c3 = "2AG32PBS3101S1202000464223GY0153".to_string(); - let now = Utc::now(); let epoch = now - Duration::hours(1)..now; - // We should never see any radio shares from owner2, since all of them are - // less than or equal to zero. - let speedtest_averages = SpeedtestAverages { - averages: HashMap::from_iter([ - ( - gw1.clone(), - SpeedtestAverage::from(vec![ - Speedtest { - report: CellSpeedtest { - pubkey: gw1.clone(), - serial: "serial-1".to_string(), - timestamp: now, - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 10, - }, - }, - Speedtest { - report: CellSpeedtest { - pubkey: gw1.clone(), - serial: "serial-1".to_string(), - timestamp: now, - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 10, - }, - }, - ]), - ), - ( - gw2.clone(), - SpeedtestAverage::from(vec![Speedtest { - report: CellSpeedtest { - pubkey: gw2.clone(), - serial: "serial-2".to_string(), - timestamp: now, - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 10, - }, - }]), - ), - ]), - }; let uuid_1 = Uuid::new_v4(); let uuid_2 = Uuid::new_v4(); From 811bbe22de1e84562fb0df2317a090c7745939e4 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 12:31:19 -0700 Subject: [PATCH 30/40] rename coverage points constructor The constructor no longer tries to calculate points, it collects information about radios and coverage. All the pointing happens in into_rewards. --- mobile_verifier/src/cli/reward_from_db.rs | 2 +- mobile_verifier/src/reward_shares.rs | 10 +++++----- mobile_verifier/src/rewarder.rs | 2 +- .../tests/integrations/boosting_oracles.rs | 2 +- .../tests/integrations/modeled_coverage.rs | 12 ++++++------ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mobile_verifier/src/cli/reward_from_db.rs b/mobile_verifier/src/cli/reward_from_db.rs index d51c48ee1..2f128c1bb 100644 --- a/mobile_verifier/src/cli/reward_from_db.rs +++ b/mobile_verifier/src/cli/reward_from_db.rs @@ -41,7 +41,7 @@ impl Cmd { let speedtest_averages = SpeedtestAverages::aggregate_epoch_averages(epoch.end, &pool).await?; - let reward_shares = CoveragePoints::aggregate_points( + let reward_shares = CoveragePoints::new( &pool, heartbeats, &speedtest_averages, diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 02cc41a07..02a78feb4 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -445,7 +445,7 @@ pub struct CoveragePoints { } impl CoveragePoints { - pub async fn aggregate_points( + pub async fn new( hex_streams: &impl CoveredHexStream, heartbeats: impl Stream>, speedtests: &SpeedtestAverages, @@ -1371,7 +1371,7 @@ mod test { let mut allocated_poc_rewards = 0_u64; let epoch = (now - Duration::hours(1))..now; - for (reward_amount, mobile_reward) in CoveragePoints::aggregate_points( + for (reward_amount, mobile_reward) in CoveragePoints::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1544,7 +1544,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::aggregate_points( + for (_reward_amount, mobile_reward) in CoveragePoints::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1675,7 +1675,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::aggregate_points( + for (_reward_amount, mobile_reward) in CoveragePoints::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1806,7 +1806,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::aggregate_points( + for (_reward_amount, mobile_reward) in CoveragePoints::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index 2ecc6934f..105df2721 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -401,7 +401,7 @@ async fn reward_poc( let verified_radio_thresholds = radio_threshold::verified_radio_thresholds(pool, reward_period).await?; - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( pool, heartbeats, &speedtest_averages, diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index c50d1e789..b4befcf7f 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -400,7 +400,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re let speedtest_avgs = SpeedtestAverages { averages }; let heartbeats = HeartbeatReward::validated(&pool, &epoch); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index e113d6b96..40e980514 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -492,7 +492,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, @@ -596,7 +596,7 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, @@ -888,7 +888,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, @@ -989,7 +989,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, @@ -1092,7 +1092,7 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, @@ -1345,7 +1345,7 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::aggregate_points( + let coverage_points = CoveragePoints::new( &pool, heartbeats, &speedtest_avgs, From 3b280983051a428534e8f2eb609a2f8d384899b2 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 12:52:31 -0700 Subject: [PATCH 31/40] remove async where possible from CoveragePoints --- mobile_verifier/src/cli/reward_from_db.rs | 1 - mobile_verifier/src/reward_shares.rs | 17 ++--- mobile_verifier/src/rewarder.rs | 5 +- .../tests/integrations/boosting_oracles.rs | 4 +- .../tests/integrations/modeled_coverage.rs | 72 +++++-------------- 5 files changed, 26 insertions(+), 73 deletions(-) diff --git a/mobile_verifier/src/cli/reward_from_db.rs b/mobile_verifier/src/cli/reward_from_db.rs index 2f128c1bb..c32b762ed 100644 --- a/mobile_verifier/src/cli/reward_from_db.rs +++ b/mobile_verifier/src/cli/reward_from_db.rs @@ -55,7 +55,6 @@ impl Cmd { let mut owner_rewards = HashMap::<_, u64>::new(); let radio_rewards = reward_shares .into_rewards(Decimal::ZERO, &epoch) - .await .ok_or(anyhow::anyhow!("no rewardable events"))?; for (_reward_amount, reward) in radio_rewards { if let Some(proto::mobile_reward_share::Reward::RadioReward(proto::RadioReward { diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 02a78feb4..cb416775b 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -557,7 +557,7 @@ impl CoveragePoints { }) } - async fn coverage_points( + fn coverage_points( &self, radio_id: &RadioId, ) -> anyhow::Result { @@ -584,7 +584,7 @@ impl CoveragePoints { Ok(coverage_points) } - pub async fn into_rewards( + pub fn into_rewards( self, available_poc_rewards: Decimal, epoch: &'_ Range>, @@ -593,7 +593,7 @@ impl CoveragePoints { let mut processed_radios = vec![]; for (radio_id, radio_info) in self.radio_infos.iter() { - let points_res = self.coverage_points(radio_id).await; + let points_res = self.coverage_points(radio_id); let points = match points_res { Ok(points) => points, Err(err) => { @@ -640,10 +640,9 @@ impl CoveragePoints { } /// Only used for testing - pub async fn test_hotspot_reward_shares(&self, hotspot: &RadioId) -> Decimal { + pub fn test_hotspot_reward_shares(&self, hotspot: &RadioId) -> Decimal { self.coverage_points(hotspot) - .await - .expect("coverage points for hotspot") + .expect("reward shares for hotspot") .reward_shares } } @@ -1382,7 +1381,6 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) - .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1555,7 +1553,6 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) - .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1686,7 +1683,6 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) - .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1817,7 +1813,6 @@ mod test { .await .unwrap() .into_rewards(total_poc_rewards, &epoch) - .await .unwrap() { let radio_reward = match mobile_reward.reward { @@ -1956,7 +1951,6 @@ mod test { let expected_hotspot = gw1; for (_reward_amount, mobile_reward) in coverage_points .into_rewards(total_poc_rewards, &epoch) - .await .expect("rewards output") { let radio_reward = match mobile_reward.reward { @@ -1981,7 +1975,6 @@ mod test { let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); assert!(coverage_points .into_rewards(total_poc_rewards, &epoch) - .await .is_none()); } diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index 105df2721..68938d370 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -411,9 +411,8 @@ async fn reward_poc( ) .await?; - let unallocated_poc_amount = if let Some(mobile_reward_shares) = coverage_points - .into_rewards(total_poc_rewards, reward_period) - .await + let unallocated_poc_amount = if let Some(mobile_reward_shares) = + coverage_points.into_rewards(total_poc_rewards, reward_period) { // handle poc reward outputs let mut allocated_poc_rewards = 0_u64; diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index b4befcf7f..e3e0fc099 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -452,9 +452,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re // = 1,073 assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner, Some(cbsd_id.clone()))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id.clone()))), dec!(1073.0) ); diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index 40e980514..f408bd8fb 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -503,9 +503,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner, Some(cbsd_id))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), dec!(250) ); @@ -607,15 +605,11 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(112.5) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(250) ); @@ -899,39 +893,27 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), dec!(250) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), dec!(0) ); @@ -1000,9 +982,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner, Some(cbsd_id))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), dec!(19) ); @@ -1103,15 +1083,11 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(19) * dec!(0.5) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(8) ); @@ -1356,39 +1332,27 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(62.5) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), dec!(0) ); assert_eq!( - coverage_points - .test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))) - .await, + coverage_points.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), dec!(0) ); From 751cedc7e684d1f464adc5b611a8e95b75a56cb3 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 12:54:03 -0700 Subject: [PATCH 32/40] remove unneccessary `this` binding --- mobile_verifier/src/reward_shares.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index cb416775b..ec15477c8 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -564,11 +564,10 @@ impl CoveragePoints { let radio_info = self.radio_infos.get(radio_id).unwrap(); let hexes = { - let this = &self; let (pubkey, cbsd_id) = radio_id; let ranked_coverage = match cbsd_id { - Some(cbsd_id) => this.coverage_map.get_cbrs_coverage(cbsd_id), - None => this.coverage_map.get_wifi_coverage(pubkey.as_ref()), + Some(cbsd_id) => self.coverage_map.get_cbrs_coverage(cbsd_id), + None => self.coverage_map.get_wifi_coverage(pubkey.as_ref()), }; ranked_coverage.to_vec() }; From e7449e1fa42802f101ef6ce35fe2b21475240b6a Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 12:58:34 -0700 Subject: [PATCH 33/40] Rename CoveragePoints -> CoverageShares This matches with the other reward structs `MapperShares`, `ServiceProviderShares`. And reflects more that we're talking about `reward_shares`, where `coverage_points` is starting to mean points provided towards shares relating only to coverage (excluding backhaul (speedtests)). --- mobile_verifier/src/cli/reward_from_db.rs | 4 ++-- mobile_verifier/src/reward_shares.rs | 16 ++++++++-------- mobile_verifier/src/rewarder.rs | 4 ++-- .../tests/integrations/boosting_oracles.rs | 4 ++-- .../tests/integrations/modeled_coverage.rs | 14 +++++++------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mobile_verifier/src/cli/reward_from_db.rs b/mobile_verifier/src/cli/reward_from_db.rs index c32b762ed..a159eb9e5 100644 --- a/mobile_verifier/src/cli/reward_from_db.rs +++ b/mobile_verifier/src/cli/reward_from_db.rs @@ -1,7 +1,7 @@ use crate::{ heartbeats::HeartbeatReward, radio_threshold::VerifiedRadioThresholds, - reward_shares::{get_scheduled_tokens_for_poc, CoveragePoints}, + reward_shares::{get_scheduled_tokens_for_poc, CoverageShares}, speedtests_average::SpeedtestAverages, Settings, }; @@ -41,7 +41,7 @@ impl Cmd { let speedtest_averages = SpeedtestAverages::aggregate_epoch_averages(epoch.end, &pool).await?; - let reward_shares = CoveragePoints::new( + let reward_shares = CoverageShares::new( &pool, heartbeats, &speedtest_averages, diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index ec15477c8..1fb750c23 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -439,12 +439,12 @@ struct RadioInfo { } #[derive(Debug)] -pub struct CoveragePoints { +pub struct CoverageShares { coverage_map: coverage_map::CoverageMap, radio_infos: HashMap, } -impl CoveragePoints { +impl CoverageShares { pub async fn new( hex_streams: &impl CoveredHexStream, heartbeats: impl Stream>, @@ -1369,7 +1369,7 @@ mod test { let mut allocated_poc_rewards = 0_u64; let epoch = (now - Duration::hours(1))..now; - for (reward_amount, mobile_reward) in CoveragePoints::new( + for (reward_amount, mobile_reward) in CoverageShares::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1541,7 +1541,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::new( + for (_reward_amount, mobile_reward) in CoverageShares::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1671,7 +1671,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::new( + for (_reward_amount, mobile_reward) in CoverageShares::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1801,7 +1801,7 @@ mod test { let duration = Duration::hours(1); let epoch = (now - duration)..now; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - for (_reward_amount, mobile_reward) in CoveragePoints::new( + for (_reward_amount, mobile_reward) in CoverageShares::new( &hex_coverage, stream::iter(heartbeat_rewards), &speedtest_avgs, @@ -1941,7 +1941,7 @@ mod test { }, ); - let coverage_points = CoveragePoints { + let coverage_points = CoverageShares { coverage_map, radio_infos, }; @@ -1965,7 +1965,7 @@ mod test { async fn skip_empty_radio_rewards() { let now = Utc::now(); let epoch = now - Duration::hours(1)..now; - let coverage_points = CoveragePoints { + let coverage_points = CoverageShares { coverage_map: coverage_map::CoverageMapBuilder::default() .build(&BoostedHexes::default(), epoch.start), radio_infos: HashMap::new(), diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index 68938d370..d4b97365d 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -3,7 +3,7 @@ use crate::{ coverage, data_session, heartbeats::{self, HeartbeatReward}, radio_threshold, - reward_shares::{self, CoveragePoints, MapperShares, ServiceProviderShares, TransferRewards}, + reward_shares::{self, CoverageShares, MapperShares, ServiceProviderShares, TransferRewards}, speedtests, speedtests_average::SpeedtestAverages, subscriber_location, telemetry, Settings, @@ -401,7 +401,7 @@ async fn reward_poc( let verified_radio_thresholds = radio_threshold::verified_radio_thresholds(pool, reward_period).await?; - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( pool, heartbeats, &speedtest_averages, diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index e3e0fc099..46a2f2f41 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -22,7 +22,7 @@ use mobile_verifier::{ ValidatedHeartbeat, }, radio_threshold::VerifiedRadioThresholds, - reward_shares::CoveragePoints, + reward_shares::CoverageShares, speedtests::Speedtest, speedtests_average::{SpeedtestAverage, SpeedtestAverages}, GatewayResolution, GatewayResolver, @@ -400,7 +400,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re let speedtest_avgs = SpeedtestAverages { averages }; let heartbeats = HeartbeatReward::validated(&pool, &epoch); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index f408bd8fb..f8cb01628 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -22,7 +22,7 @@ use mobile_verifier::{ ValidatedHeartbeat, }, radio_threshold::VerifiedRadioThresholds, - reward_shares::CoveragePoints, + reward_shares::CoverageShares, speedtests::Speedtest, speedtests_average::{SpeedtestAverage, SpeedtestAverages}, GatewayResolution, GatewayResolver, IsAuthorized, @@ -492,7 +492,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -594,7 +594,7 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -882,7 +882,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -971,7 +971,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -1072,7 +1072,7 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -1321,7 +1321,7 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoveragePoints::new( + let coverage_points = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, From 8ea92d439b57cd314c1205d97c9cc57bcd9d87d4 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 13:05:04 -0700 Subject: [PATCH 34/40] remove unneeded derive for debug --- mobile_verifier/src/speedtests_average.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile_verifier/src/speedtests_average.rs b/mobile_verifier/src/speedtests_average.rs index 04337673d..115bf0537 100644 --- a/mobile_verifier/src/speedtests_average.rs +++ b/mobile_verifier/src/speedtests_average.rs @@ -199,7 +199,7 @@ impl SpeedtestTier { } } -#[derive(Debug, Clone, Default)] +#[derive(Clone, Default)] pub struct SpeedtestAverages { pub averages: HashMap, } From f7cc0a541dc13c5e0c3d1b727acc2f5eb6da1ec9 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 15:34:39 -0700 Subject: [PATCH 35/40] remove location trust score calculation This all lives in the coverage-point-calculator now --- mobile_verifier/src/heartbeats/mod.rs | 73 --------------------------- 1 file changed, 73 deletions(-) diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index fd2a7c0a5..ea9cddc79 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -278,8 +278,6 @@ pub struct HeartbeatReward { pub coverage_object: Uuid, } -const RESTRICTIVE_MAX_DISTANCE: i64 = 50; - impl HeartbeatReward { pub fn key(&self) -> KeyType<'_> { match self.cbsd_id { @@ -299,39 +297,6 @@ impl HeartbeatReward { } } - pub fn trust_score_multiplier(&self, overlaps_boosted: bool) -> Decimal { - if self.cbsd_id.is_some() { - // If this is a cbrs radio, the trust score is always 1 - return dec!(1.0); - } - if overlaps_boosted { - // If we overlap a boosted hex, use the more restrictive distance - // check: - let distances = self.distances_to_asserted.as_ref().unwrap(); - let num_distances = Decimal::from(distances.len()); - distances - .iter() - .zip(self.trust_score_multipliers.iter()) - .map(|(distance, ts)| { - std::cmp::min( - if *distance > RESTRICTIVE_MAX_DISTANCE { - dec!(0.25) - } else { - dec!(1.0) - }, - *ts, - ) - }) - .sum::() - / num_distances - } else { - // If we don't overlap a boosted hex, just use the average of the - // trust scores: - let num_trust_scores = Decimal::from(self.trust_score_multipliers.len()); - self.trust_score_multipliers.iter().sum::() / num_trust_scores - } - } - pub fn validated<'a>( exec: impl sqlx::PgExecutor<'a> + Copy + 'a, epoch: &'a Range>, @@ -959,44 +924,6 @@ mod test { use super::*; use proto::SeniorityUpdateReason::*; - #[test] - fn ensure_stricter_distance_check_in_trust_score_for_boosted_hexes() { - let mut heartbeat_reward = HeartbeatReward { - hotspot_key: "11sctWiP9r5wDJVuDe1Th4XSL2vaawaLLSQF8f8iokAoMAJHxqp" - .parse() - .unwrap(), - cbsd_id: None, - cell_type: CellType::CellTypeNone, - distances_to_asserted: Some(vec![RESTRICTIVE_MAX_DISTANCE + 1]), - trust_score_multipliers: vec![dec!(1.0)], - coverage_object: Uuid::new_v4(), - }; - // If the heartbeat is not in a boosted hex, the trust score should be 1.0: - assert_eq!(heartbeat_reward.trust_score_multiplier(false), dec!(1.0)); - // If the heartbeat does overlap a boosted hex, the trust score should be 0.25: - assert_eq!(heartbeat_reward.trust_score_multiplier(true), dec!(0.25)); - // Now we check that if we set the distance to asserted to be below the restrictive - // max, that we have a trust score of 1.0: - heartbeat_reward.distances_to_asserted = Some(vec![RESTRICTIVE_MAX_DISTANCE]); - assert_eq!(heartbeat_reward.trust_score_multiplier(true), dec!(1.0)); - } - - #[test] - fn test_averaging_of_trust_scores() { - let heartbeat_reward = HeartbeatReward { - hotspot_key: "11sctWiP9r5wDJVuDe1Th4XSL2vaawaLLSQF8f8iokAoMAJHxqp" - .parse() - .unwrap(), - cbsd_id: None, - cell_type: CellType::CellTypeNone, - distances_to_asserted: Some(vec![RESTRICTIVE_MAX_DISTANCE + 1, 0, 0, 0, 0]), - trust_score_multipliers: vec![dec!(1.0), dec!(0.25), dec!(1.0), dec!(1.0), dec!(0.25)], - coverage_object: Uuid::new_v4(), - }; - assert_eq!(heartbeat_reward.trust_score_multiplier(false), dec!(0.7)); - assert_eq!(heartbeat_reward.trust_score_multiplier(true), dec!(0.55)); - } - fn heartbeat(timestamp: DateTime, coverage_object: Uuid) -> ValidatedHeartbeat { ValidatedHeartbeat { cell_type: CellType::CellTypeNone, From 4c5fa59c892aa62386e4b77fd93d0ad4807c9b32 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 15:49:01 -0700 Subject: [PATCH 36/40] rename to differentiate argument from local bindings --- mobile_verifier/src/reward_shares.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 1fb750c23..75d974adc 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -448,7 +448,7 @@ impl CoverageShares { pub async fn new( hex_streams: &impl CoveredHexStream, heartbeats: impl Stream>, - speedtests: &SpeedtestAverages, + speedtest_averages: &SpeedtestAverages, boosted_hexes: &BoostedHexes, verified_radio_thresholds: &VerifiedRadioThresholds, reward_period: &Range>, @@ -507,7 +507,7 @@ impl CoverageShares { }); use coverage_point_calculator::{BytesPs, Speedtest}; - let speedtests = speedtests + let speedtests = speedtest_averages .get_average(&pubkey) .unwrap() .speedtests From c93159b6ce479f1b2e6814fb1b97a9edf828aa30 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 15:48:37 -0700 Subject: [PATCH 37/40] construct coverage map inside block scope We can insert coverage objects directly after iterating over them, radio_info only needs to know if the radio `is_indoor`. Moving that binding to the top helps hint that it's set by something in the block and returned. And we don't need to carry around covered_hexes for any longer than necessary. --- mobile_verifier/src/reward_shares.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 75d974adc..6c306819f 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -468,14 +468,14 @@ impl CoverageShares { .fetch_seniority(heartbeat_key, reward_period.end) .await?; - let (is_indoor, covered_hexes) = { + let is_indoor = { + let mut is_indoor = false; + let covered_hexes_stream = hex_streams .covered_hex_stream(heartbeat_key, &heartbeat.coverage_object, &seniority) .await?; let mut covered_hexes = vec![]; - - let mut is_indoor = false; let mut covered_hexes_stream = std::pin::pin!(covered_hexes_stream); while let Some(hex_coverage) = covered_hexes_stream.next().await.transpose()? { is_indoor = hex_coverage.indoor; @@ -487,7 +487,15 @@ impl CoverageShares { }); } - (is_indoor, covered_hexes) + coverage_map_builder.insert_coverage_object(coverage_map::CoverageObject { + indoor: is_indoor, + hotspot_key: pubkey.clone().into(), + cbsd_id: cbsd_id.clone(), + seniority_timestamp: seniority.seniority_ts, + coverage: covered_hexes, + }); + + is_indoor }; use coverage_point_calculator::RadioType; @@ -498,14 +506,6 @@ impl CoverageShares { (false, Some(_)) => RadioType::OutdoorCbrs, }; - coverage_map_builder.insert_coverage_object(coverage_map::CoverageObject { - indoor: is_indoor, - hotspot_key: pubkey.clone().into(), - cbsd_id: cbsd_id.clone(), - seniority_timestamp: seniority.seniority_ts, - coverage: covered_hexes, - }); - use coverage_point_calculator::{BytesPs, Speedtest}; let speedtests = speedtest_averages .get_average(&pubkey) From eb1b86999b95956922982e461483680552db61c8 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 13 Jun 2024 15:49:23 -0700 Subject: [PATCH 38/40] function call now small enough to inline --- mobile_verifier/src/reward_shares.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index 6c306819f..d42bc928e 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -592,8 +592,7 @@ impl CoverageShares { let mut processed_radios = vec![]; for (radio_id, radio_info) in self.radio_infos.iter() { - let points_res = self.coverage_points(radio_id); - let points = match points_res { + let points = match self.coverage_points(radio_id) { Ok(points) => points, Err(err) => { tracing::error!( From 360981d69c11e6acfbbc135b71e8f14d420ac472 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 14 Jun 2024 15:08:47 -0700 Subject: [PATCH 39/40] rename coverage variables to match with struct --- mobile_verifier/src/reward_shares.rs | 8 ++-- mobile_verifier/src/rewarder.rs | 4 +- .../tests/integrations/boosting_oracles.rs | 4 +- .../tests/integrations/modeled_coverage.rs | 48 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index d42bc928e..f052f1167 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -1940,14 +1940,14 @@ mod test { }, ); - let coverage_points = CoverageShares { + let coverage_shares = CoverageShares { coverage_map, radio_infos, }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); // gw2 does not have enough speedtests for a mulitplier let expected_hotspot = gw1; - for (_reward_amount, mobile_reward) in coverage_points + for (_reward_amount, mobile_reward) in coverage_shares .into_rewards(total_poc_rewards, &epoch) .expect("rewards output") { @@ -1964,14 +1964,14 @@ mod test { async fn skip_empty_radio_rewards() { let now = Utc::now(); let epoch = now - Duration::hours(1)..now; - let coverage_points = CoverageShares { + let coverage_shares = CoverageShares { coverage_map: coverage_map::CoverageMapBuilder::default() .build(&BoostedHexes::default(), epoch.start), radio_infos: HashMap::new(), }; let total_poc_rewards = get_scheduled_tokens_for_poc(epoch.end - epoch.start); - assert!(coverage_points + assert!(coverage_shares .into_rewards(total_poc_rewards, &epoch) .is_none()); } diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index d4b97365d..e0ed055e9 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -401,7 +401,7 @@ async fn reward_poc( let verified_radio_thresholds = radio_threshold::verified_radio_thresholds(pool, reward_period).await?; - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( pool, heartbeats, &speedtest_averages, @@ -412,7 +412,7 @@ async fn reward_poc( .await?; let unallocated_poc_amount = if let Some(mobile_reward_shares) = - coverage_points.into_rewards(total_poc_rewards, reward_period) + coverage_shares.into_rewards(total_poc_rewards, reward_period) { // handle poc reward outputs let mut allocated_poc_rewards = 0_u64; diff --git a/mobile_verifier/tests/integrations/boosting_oracles.rs b/mobile_verifier/tests/integrations/boosting_oracles.rs index 46a2f2f41..1436437b5 100644 --- a/mobile_verifier/tests/integrations/boosting_oracles.rs +++ b/mobile_verifier/tests/integrations/boosting_oracles.rs @@ -400,7 +400,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re let speedtest_avgs = SpeedtestAverages { averages }; let heartbeats = HeartbeatReward::validated(&pool, &epoch); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -452,7 +452,7 @@ async fn test_footfall_and_urbanization_and_landtype(pool: PgPool) -> anyhow::Re // = 1,073 assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id.clone()))), + coverage_shares.test_hotspot_reward_shares(&(owner, Some(cbsd_id.clone()))), dec!(1073.0) ); diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index f8cb01628..e8ab799ba 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -492,7 +492,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -503,7 +503,7 @@ async fn scenario_one(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), + coverage_shares.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), dec!(250) ); @@ -594,7 +594,7 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -605,11 +605,11 @@ async fn scenario_two(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), + coverage_shares.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(112.5) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), + coverage_shares.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(250) ); @@ -882,7 +882,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -893,27 +893,27 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), + coverage_shares.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), + coverage_shares.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), + coverage_shares.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), + coverage_shares.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), dec!(250) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), + coverage_shares.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), + coverage_shares.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), dec!(0) ); @@ -971,7 +971,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -982,7 +982,7 @@ async fn scenario_four(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), + coverage_shares.test_hotspot_reward_shares(&(owner, Some(cbsd_id))), dec!(19) ); @@ -1072,7 +1072,7 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -1083,11 +1083,11 @@ async fn scenario_five(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), + coverage_shares.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(19) * dec!(0.5) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), + coverage_shares.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(8) ); @@ -1321,7 +1321,7 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); - let coverage_points = CoverageShares::new( + let coverage_shares = CoverageShares::new( &pool, heartbeats, &speedtest_avgs, @@ -1332,27 +1332,27 @@ async fn scenario_six(pool: PgPool) -> anyhow::Result<()> { .await?; assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), + coverage_shares.test_hotspot_reward_shares(&(owner_1, Some(cbsd_id_1))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), + coverage_shares.test_hotspot_reward_shares(&(owner_2, Some(cbsd_id_2))), dec!(62.5) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), + coverage_shares.test_hotspot_reward_shares(&(owner_3, Some(cbsd_id_3))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), + coverage_shares.test_hotspot_reward_shares(&(owner_4, Some(cbsd_id_4))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), + coverage_shares.test_hotspot_reward_shares(&(owner_5, Some(cbsd_id_5))), dec!(0) ); assert_eq!( - coverage_points.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), + coverage_shares.test_hotspot_reward_shares(&(owner_6, Some(cbsd_id_6))), dec!(0) ); From 07f864ca7ec6cd1ec4456d0a48ad40b139a94543 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Mon, 17 Jun 2024 11:19:02 -0700 Subject: [PATCH 40/40] handle potential lack of speedtest for radio --- mobile_verifier/src/reward_shares.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index f052f1167..d3608a843 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -507,10 +507,11 @@ impl CoverageShares { }; use coverage_point_calculator::{BytesPs, Speedtest}; - let speedtests = speedtest_averages - .get_average(&pubkey) - .unwrap() - .speedtests + let speedtests = match speedtest_averages.get_average(&pubkey) { + Some(avg) => avg.speedtests, + None => vec![], + }; + let speedtests = speedtests .iter() .map(|test| Speedtest { upload_speed: BytesPs::new(test.report.upload_speed),