From 9abbab81611f6df52367dea546098ee2300d92ac Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 24 Apr 2024 09:55:42 -0700 Subject: [PATCH 01/15] Don't return result for operation that cannot fail --- mobile_config/src/boosted_hex_info.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index cedb34fd9..bb8aafe0b 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -78,11 +78,11 @@ impl TryFrom for BoostedHexInfoProto { } impl BoostedHexInfo { - pub fn current_multiplier(&self, ts: DateTime) -> anyhow::Result> { + pub fn current_multiplier(&self, ts: DateTime) -> Option { if self.end_ts.is_some() && ts >= self.end_ts.unwrap() { // end time has been set and the current time is after the end time, so return None // to indicate that the hex is no longer boosted - return Ok(None); + return None; }; if self.start_ts.is_some() { // start time has previously been set, so we can calculate the current multiplier @@ -93,11 +93,11 @@ impl BoostedHexInfo { .num_seconds() .checked_div(self.period_length.num_seconds()) .unwrap_or(0) as usize; - Ok(Some(self.multipliers[index])) + Some(self.multipliers[index]) } else { // start time has not been previously set, assume this is the first time rewarding this hex // and use the first multiplier - Ok(Some(self.multipliers[0])) + Some(self.multipliers[0]) } } } @@ -173,7 +173,7 @@ impl BoostedHexes { pub fn get_current_multiplier(&self, location: Cell, ts: DateTime) -> Option { self.hexes .get(&location) - .and_then(|info| info.current_multiplier(ts).ok()?) + .and_then(|info| info.current_multiplier(ts)) } } From 3836e934079f9372e516237f0760a508173c9191 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 24 Apr 2024 10:05:20 -0700 Subject: [PATCH 02/15] Refactor to use BoostedHexes methods Making the internal member hexes private will make it easier to change the implementation when device type is introduced. --- boost_manager/src/activator.rs | 2 +- boost_manager/src/watcher.rs | 4 +- .../tests/integrations/activator_tests.rs | 29 ++------ mobile_config/src/boosted_hex_info.rs | 18 ++++- .../tests/integrations/modeled_coverage.rs | 71 ++++++++----------- 5 files changed, 55 insertions(+), 69 deletions(-) diff --git a/boost_manager/src/activator.rs b/boost_manager/src/activator.rs index 796a772d4..8761aab3f 100644 --- a/boost_manager/src/activator.rs +++ b/boost_manager/src/activator.rs @@ -130,7 +130,7 @@ pub async fn process_boosted_hex( boosted_hexes: &BoostedHexes, hex: &BoostedHex, ) -> Result<()> { - match boosted_hexes.hexes.get(&hex.location) { + match boosted_hexes.get(&hex.location) { Some(info) => { if info.start_ts.is_none() { db::insert_activated_hex( diff --git a/boost_manager/src/watcher.rs b/boost_manager/src/watcher.rs index c669cb1f2..a54321f0a 100644 --- a/boost_manager/src/watcher.rs +++ b/boost_manager/src/watcher.rs @@ -87,9 +87,9 @@ where tracing::info!( "modified hexes count since {}: {} ", last_processed_ts, - boosted_hexes.hexes.len() + boosted_hexes.count() ); - for info in boosted_hexes.hexes.values() { + for info in boosted_hexes.iter_hexes() { let proto: BoostedHexUpdateProto = BoostedHexUpdateProto { timestamp: now.encode_timestamp(), update: Some(info.clone().try_into()?), diff --git a/boost_manager/tests/integrations/activator_tests.rs b/boost_manager/tests/integrations/activator_tests.rs index 394df1b64..ae842fc70 100644 --- a/boost_manager/tests/integrations/activator_tests.rs +++ b/boost_manager/tests/integrations/activator_tests.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Duration as ChronoDuration, Duration, Timelike, Utc}; use mobile_config::boosted_hex_info::{BoostedHex, BoostedHexInfo, BoostedHexes}; use solana_sdk::pubkey::Pubkey; use sqlx::PgPool; -use std::{collections::HashMap, num::NonZeroU32, str::FromStr}; +use std::{num::NonZeroU32, str::FromStr}; const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU"; const BOOST_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG"; @@ -82,14 +82,7 @@ impl TestContext { async fn test_activated_hex_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now(); let ctx = TestContext::setup(now)?; - let boosted_hexes_map = ctx - .boosted_hexes - .iter() - .map(|info| (info.location, info.clone())) - .collect::>(); - let boosted_hexes = BoostedHexes { - hexes: boosted_hexes_map, - }; + let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); // test a boosted hex derived from radio rewards // with a non set start date, will result in a row being @@ -119,14 +112,7 @@ async fn test_activated_hex_insert(pool: PgPool) -> anyhow::Result<()> { async fn test_activated_hex_no_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now(); let ctx = TestContext::setup(now)?; - let boosted_hexes_map = ctx - .boosted_hexes - .iter() - .map(|info| (info.location, info.clone())) - .collect::>(); - let boosted_hexes = BoostedHexes { - hexes: boosted_hexes_map, - }; + let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); // test a boosted hex derived from radio rewards // with an active start date, will result in no row being @@ -152,14 +138,7 @@ async fn test_activated_hex_no_insert(pool: PgPool) -> anyhow::Result<()> { async fn test_activated_dup_hex_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now().with_second(0).unwrap(); let ctx = TestContext::setup(now)?; - let boosted_hexes_map = ctx - .boosted_hexes - .iter() - .map(|info| (info.location, info.clone())) - .collect::>(); - let boosted_hexes = BoostedHexes { - hexes: boosted_hexes_map, - }; + let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); // test with DUPLICATE boosted hexes derived from radio rewards // with a non set start date, will result in a single row being diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index bb8aafe0b..df2082192 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -104,7 +104,7 @@ impl BoostedHexInfo { #[derive(Debug, Clone, Default)] pub struct BoostedHexes { - pub hexes: HashMap, + hexes: HashMap, } #[derive(PartialEq, Debug, Clone)] @@ -175,6 +175,22 @@ impl BoostedHexes { .get(&location) .and_then(|info| info.current_multiplier(ts)) } + + pub fn count(&self) -> usize { + self.hexes.len() + } + + pub fn iter_hexes(&self) -> impl Iterator { + self.hexes.values() + } + + pub fn get(&self, location: &Cell) -> Option<&BoostedHexInfo> { + self.hexes.get(location) + } + + pub fn insert(&mut self, info: BoostedHexInfo) { + self.hexes.insert(info.location, info); + } } impl coverage_map::BoostedHexMap for BoostedHexes { diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index bb9c406f9..c5ae9e4f0 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -841,46 +841,37 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { let speedtest_avgs = SpeedtestAverages { averages }; let mut boosted_hexes = BoostedHexes::default(); - boosted_hexes.hexes.insert( - Cell::from_raw(0x8a1fb466d2dffff)?, - BoostedHexInfo { - location: Cell::from_raw(0x8a1fb466d2dffff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(1).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - }, - ); - boosted_hexes.hexes.insert( - Cell::from_raw(0x8a1fb49642dffff)?, - BoostedHexInfo { - location: Cell::from_raw(0x8a1fb49642dffff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(2).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - }, - ); - boosted_hexes.hexes.insert( - Cell::from_raw(0x8c2681a306607ff)?, - BoostedHexInfo { - // hotspot 1's location - location: Cell::from_raw(0x8c2681a306607ff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(3).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - }, - ); + boosted_hexes.insert(BoostedHexInfo { + location: Cell::from_raw(0x8a1fb466d2dffff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(1).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + }); + boosted_hexes.insert(BoostedHexInfo { + location: Cell::from_raw(0x8a1fb49642dffff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(2).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + }); + boosted_hexes.insert(BoostedHexInfo { + // hotspot 1's location + location: Cell::from_raw(0x8c2681a306607ff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(3).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + }); let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); From ed652e261fd4054bceaf73738dabce6953c37fe7 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 24 Apr 2024 16:46:01 -0700 Subject: [PATCH 03/15] Add device type to boosted hex info --- .../tests/integrations/activator_tests.rs | 7 ++- .../tests/integrations/watcher_tests.rs | 7 ++- mobile_config/src/boosted_hex_info.rs | 62 +++++++++++++++++++ .../tests/integrations/hex_boosting.rs | 23 ++++++- .../tests/integrations/modeled_coverage.rs | 5 +- 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/boost_manager/tests/integrations/activator_tests.rs b/boost_manager/tests/integrations/activator_tests.rs index ae842fc70..0212fcc26 100644 --- a/boost_manager/tests/integrations/activator_tests.rs +++ b/boost_manager/tests/integrations/activator_tests.rs @@ -1,6 +1,8 @@ use boost_manager::{activator, db, OnChainStatus}; use chrono::{DateTime, Duration as ChronoDuration, Duration, Timelike, Utc}; -use mobile_config::boosted_hex_info::{BoostedHex, BoostedHexInfo, BoostedHexes}; +use mobile_config::boosted_hex_info::{ + BoostedHex, BoostedHexDeviceType, BoostedHexInfo, BoostedHexes, +}; use solana_sdk::pubkey::Pubkey; use sqlx::PgPool; use std::{num::NonZeroU32, str::FromStr}; @@ -50,6 +52,7 @@ impl TestContext { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { location: 0x8a1fb49642dffff_u64.try_into().expect("valid h3 cell"), @@ -60,6 +63,7 @@ impl TestContext { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -71,6 +75,7 @@ impl TestContext { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; Ok(Self { diff --git a/boost_manager/tests/integrations/watcher_tests.rs b/boost_manager/tests/integrations/watcher_tests.rs index 64f42c0a4..78d180eca 100644 --- a/boost_manager/tests/integrations/watcher_tests.rs +++ b/boost_manager/tests/integrations/watcher_tests.rs @@ -2,7 +2,10 @@ use crate::common::{self, MockFileSinkReceiver, MockHexBoostingClient}; use boost_manager::watcher::{self, Watcher}; use chrono::{Duration as ChronoDuration, Duration, Utc}; use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto; -use mobile_config::boosted_hex_info::BoostedHexInfo; +use mobile_config::{ + boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo, BoostedHexInfoStream}, + client::{hex_boosting_client::HexBoostingInfoResolver, ClientError}, +}; use solana_sdk::pubkey::Pubkey; use sqlx::PgPool; use std::{num::NonZeroU32, str::FromStr}; @@ -45,6 +48,7 @@ async fn test_boosted_hex_updates_to_filestore(pool: PgPool) -> anyhow::Result<( boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { location: 0x8a1fb49642dffff_u64.try_into().expect("valid h3 cell"), @@ -55,6 +59,7 @@ async fn test_boosted_hex_updates_to_filestore(pool: PgPool) -> anyhow::Result<( boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index df2082192..a6db29f35 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Duration, Utc}; use file_store::traits::TimestampDecode; use futures::stream::{BoxStream, StreamExt}; use helium_proto::services::poc_mobile::BoostedHex as BoostedHexProto; +use helium_proto::BoostedHexDeviceTypeV1 as BoostedHexDeviceTypeProto; use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto; use hextree::Cell; use solana_sdk::pubkey::Pubkey; @@ -24,12 +25,14 @@ pub struct BoostedHexInfo { pub boosted_hex_pubkey: Pubkey, pub boost_config_pubkey: Pubkey, pub version: u32, + pub device_type: BoostedHexDeviceType, } impl TryFrom for BoostedHexInfo { type Error = anyhow::Error; fn try_from(v: BoostedHexInfoProto) -> anyhow::Result { let period_length = Duration::seconds(v.period_length as i64); + let device_type = v.device_type(); let multipliers = v .multipliers .into_iter() @@ -49,6 +52,7 @@ impl TryFrom for BoostedHexInfo { boosted_hex_pubkey, boost_config_pubkey, version: v.version, + device_type: device_type.into(), }) } } @@ -73,6 +77,7 @@ impl TryFrom for BoostedHexInfoProto { boosted_hex_pubkey: v.boosted_hex_pubkey.to_bytes().into(), boost_config_pubkey: v.boost_config_pubkey.to_bytes().into(), version: v.version, + device_type: v.device_type.into(), }) } } @@ -102,6 +107,53 @@ impl BoostedHexInfo { } } +#[derive(Debug, Clone)] +pub enum BoostedHexDeviceType { + All, + CbrsIndoor, + CbrsOutdoor, + WifiIndoor, + WifiOutdoor, +} + +impl From for BoostedHexDeviceType { + fn from(device_type_proto: BoostedHexDeviceTypeProto) -> Self { + match device_type_proto { + BoostedHexDeviceTypeProto::All => Self::All, + BoostedHexDeviceTypeProto::CbrsIndoor => Self::CbrsIndoor, + BoostedHexDeviceTypeProto::CbrsOutdoor => Self::CbrsOutdoor, + BoostedHexDeviceTypeProto::WifiIndoor => Self::WifiIndoor, + BoostedHexDeviceTypeProto::WifiOutdoor => Self::WifiOutdoor, + } + } +} + +impl From for BoostedHexDeviceTypeProto { + fn from(device_type: BoostedHexDeviceType) -> Self { + match device_type { + BoostedHexDeviceType::All => Self::All, + BoostedHexDeviceType::CbrsIndoor => Self::CbrsIndoor, + BoostedHexDeviceType::CbrsOutdoor => Self::CbrsOutdoor, + BoostedHexDeviceType::WifiIndoor => Self::WifiIndoor, + BoostedHexDeviceType::WifiOutdoor => Self::WifiOutdoor, + } + } +} + +impl From for i32 { + fn from(device_type: BoostedHexDeviceType) -> Self { + BoostedHexDeviceTypeProto::from(device_type) as i32 + } +} + +impl TryFrom for BoostedHexDeviceType { + type Error = helium_proto::DecodeError; + + fn try_from(db_value: i32) -> Result { + Ok(BoostedHexDeviceTypeProto::try_from(db_value)?.into()) + } +} + #[derive(Debug, Clone, Default)] pub struct BoostedHexes { hexes: HashMap, @@ -280,6 +332,12 @@ pub(crate) mod db { let location = Cell::try_from(row.get::("location")) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + let device_type = match row.get::, &str>("device_type") { + None => super::BoostedHexDeviceType::All, + Some(val) => super::BoostedHexDeviceType::try_from(val) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?, + }; + Ok(Self { location, start_ts, @@ -289,6 +347,7 @@ pub(crate) mod db { boosted_hex_pubkey, boost_config_pubkey, version, + device_type, }) } } @@ -337,6 +396,7 @@ mod tests { .to_bytes() .to_vec(), version: 1, + device_type: BoostedHexDeviceTypeProto::All.into(), }; let msg = BoostedHexInfo::try_from(proto)?; @@ -378,6 +438,7 @@ mod tests { .to_bytes() .to_vec(), version: 1, + device_type: BoostedHexDeviceTypeProto::All.into(), }; let msg = BoostedHexInfo::try_from(proto)?; @@ -413,6 +474,7 @@ mod tests { boosted_hex_pubkey: BOOST_HEX_PUBKEY.as_bytes().to_vec(), boost_config_pubkey: BOOST_HEX_CONFIG_PUBKEY.as_bytes().to_vec(), version: 1, + device_type: BoostedHexDeviceTypeProto::All.into(), }; assert_eq!( "multipliers cannot contain values of 0", diff --git a/mobile_verifier/tests/integrations/hex_boosting.rs b/mobile_verifier/tests/integrations/hex_boosting.rs index ae11f4363..bfbe42343 100644 --- a/mobile_verifier/tests/integrations/hex_boosting.rs +++ b/mobile_verifier/tests/integrations/hex_boosting.rs @@ -14,7 +14,10 @@ use helium_proto::services::{ }, }; use hextree::Cell; -use mobile_config::boosted_hex_info::BoostedHexInfo; +use mobile_config::{ + boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo, BoostedHexInfoStream}, + client::{hex_boosting_client::HexBoostingInfoResolver, ClientError}, +}; use mobile_verifier::{ cell_type::CellType, coverage::CoverageObject, @@ -106,6 +109,7 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 2's location @@ -117,6 +121,7 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -128,6 +133,7 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -294,6 +300,7 @@ async fn test_poc_boosted_hexes_thresholds_not_met(pool: PgPool) -> anyhow::Resu boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 2's location @@ -305,6 +312,7 @@ async fn test_poc_boosted_hexes_thresholds_not_met(pool: PgPool) -> anyhow::Resu boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -316,6 +324,7 @@ async fn test_poc_boosted_hexes_thresholds_not_met(pool: PgPool) -> anyhow::Resu boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -442,6 +451,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 1's second covered location @@ -453,6 +463,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 2's location @@ -464,6 +475,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -475,6 +487,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -643,6 +656,7 @@ async fn test_expired_boosted_hex(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { location: Cell::from_raw(0x8a1fb49642dffff_u64)?, @@ -653,6 +667,7 @@ async fn test_expired_boosted_hex(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -771,6 +786,7 @@ async fn test_reduced_location_score_with_boosted_hexes(pool: PgPool) -> anyhow: boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -782,6 +798,7 @@ async fn test_reduced_location_score_with_boosted_hexes(pool: PgPool) -> anyhow: boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -1134,6 +1151,7 @@ async fn test_poc_with_cbrs_and_multi_coverage_boosted_hexes(pool: PgPool) -> an boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 1's second covered location @@ -1145,6 +1163,7 @@ async fn test_poc_with_cbrs_and_multi_coverage_boosted_hexes(pool: PgPool) -> an boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 2's location @@ -1156,6 +1175,7 @@ async fn test_poc_with_cbrs_and_multi_coverage_boosted_hexes(pool: PgPool) -> an boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -1167,6 +1187,7 @@ async fn test_poc_with_cbrs_and_multi_coverage_boosted_hexes(pool: PgPool) -> an boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index c5ae9e4f0..c4f846fd7 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -13,7 +13,7 @@ use helium_proto::services::{ poc_mobile::{CoverageObjectValidity, LocationSource, SignalLevel}, }; use hextree::Cell; -use mobile_config::boosted_hex_info::{BoostedHexInfo, BoostedHexes}; +use mobile_config::boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo, BoostedHexes}; use mobile_verifier::{ coverage::{CoverageClaimTimeCache, CoverageObject, CoverageObjectCache}, @@ -850,6 +850,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }); boosted_hexes.insert(BoostedHexInfo { location: Cell::from_raw(0x8a1fb49642dffff)?, @@ -860,6 +861,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }); boosted_hexes.insert(BoostedHexInfo { // hotspot 1's location @@ -871,6 +873,7 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }); let reward_period = start..end; From 303aab14a69a481b601baae2c617fe416d60c6b8 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 25 Apr 2024 12:48:39 -0700 Subject: [PATCH 04/15] Add boosted hexes ability to accumulate multipliers A cell can be boosted more than once. Old boosts will retain device_type of ::All. Any boost matching the device_type accumulates into the multiplier. ex: 3 boost with a multiplier of 10, will multiply by 30. --- boost_manager/src/activator.rs | 22 +++-- mobile_config/src/boosted_hex_info.rs | 126 +++++++++++++++++++++----- 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/boost_manager/src/activator.rs b/boost_manager/src/activator.rs index 8761aab3f..7d8f3868a 100644 --- a/boost_manager/src/activator.rs +++ b/boost_manager/src/activator.rs @@ -131,16 +131,18 @@ pub async fn process_boosted_hex( hex: &BoostedHex, ) -> Result<()> { match boosted_hexes.get(&hex.location) { - Some(info) => { - if info.start_ts.is_none() { - db::insert_activated_hex( - txn, - hex.location.into_raw(), - &info.boosted_hex_pubkey.to_string(), - &info.boost_config_pubkey.to_string(), - manifest_time, - ) - .await?; + Some(hexes) => { + for info in hexes { + if info.start_ts.is_none() { + db::insert_activated_hex( + txn, + hex.location.into_raw(), + &info.boosted_hex_pubkey.to_string(), + &info.boost_config_pubkey.to_string(), + manifest_time, + ) + .await?; + } } } None => { diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index a6db29f35..ab64d2499 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -105,9 +105,13 @@ impl BoostedHexInfo { Some(self.multipliers[0]) } } + + fn matches_device_type(&self, device_type: &BoostedHexDeviceType) -> bool { + self.device_type == *device_type || self.device_type == BoostedHexDeviceType::All + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum BoostedHexDeviceType { All, CbrsIndoor, @@ -154,11 +158,6 @@ impl TryFrom for BoostedHexDeviceType { } } -#[derive(Debug, Clone, Default)] -pub struct BoostedHexes { - hexes: HashMap, -} - #[derive(PartialEq, Debug, Clone)] pub struct BoostedHex { pub location: Cell, @@ -180,27 +179,33 @@ impl TryFrom for BoostedHex { } } +#[derive(Debug, Clone, Default)] +pub struct BoostedHexes { + hexes: HashMap>, +} + impl BoostedHexes { pub fn new(hexes: Vec) -> Self { - let hexes = hexes - .into_iter() - .map(|info| (info.location, info)) - .collect(); - Self { hexes } + let mut me = Self::default(); + for hex in hexes { + me.insert(hex); + } + me } pub async fn get_all( hex_service_client: &impl HexBoostingInfoResolver, ) -> anyhow::Result { - let mut map = HashMap::new(); let mut stream = hex_service_client .clone() .stream_boosted_hexes_info() .await?; + + let mut me = Self::default(); while let Some(info) = stream.next().await { - map.insert(info.location, info); + me.insert(info); } - Ok(Self { hexes: map }) + Ok(me) } pub fn is_boosted(&self, location: &Cell) -> bool { @@ -211,21 +216,34 @@ impl BoostedHexes { hex_service_client: &impl HexBoostingInfoResolver, timestamp: DateTime, ) -> anyhow::Result { - let mut map = HashMap::new(); let mut stream = hex_service_client .clone() .stream_modified_boosted_hexes_info(timestamp) .await?; + + let mut me = Self::default(); while let Some(info) = stream.next().await { - map.insert(info.location, info); + me.insert(info); } - Ok(Self { hexes: map }) + Ok(me) } - pub fn get_current_multiplier(&self, location: Cell, ts: DateTime) -> Option { - self.hexes - .get(&location) - .and_then(|info| info.current_multiplier(ts)) + pub fn get_current_multiplier( + &self, + location: Cell, + device_type: BoostedHexDeviceType, + ts: DateTime, + ) -> Option { + let current_multiplier = self + .hexes + .get(&location)? + .iter() + .filter(|info| info.matches_device_type(&device_type)) + .flat_map(|info| info.current_multiplier(ts)) + .map(|x| x.get()) + .sum::(); + + NonZeroU32::new(current_multiplier) } pub fn count(&self) -> usize { @@ -233,15 +251,15 @@ impl BoostedHexes { } pub fn iter_hexes(&self) -> impl Iterator { - self.hexes.values() + self.hexes.values().flatten() } - pub fn get(&self, location: &Cell) -> Option<&BoostedHexInfo> { + pub fn get(&self, location: &Cell) -> Option<&Vec> { self.hexes.get(location) } pub fn insert(&mut self, info: BoostedHexInfo) { - self.hexes.insert(info.location, info); + self.hexes.entry(info.location).or_default().push(info); } } @@ -379,6 +397,66 @@ mod tests { const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU"; const BOOST_HEX_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG"; + #[test] + fn boosted_hexes_accumulate_multipliers() -> anyhow::Result<()> { + let cell = Cell::from_raw(631252734740306943)?; + let now = Utc::now(); + + let hexes = vec![ + BoostedHexInfo { + location: cell, + start_ts: None, + end_ts: None, + period_length: Duration::seconds(2592000), + multipliers: vec![NonZeroU32::new(2).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, + boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)?, + version: 0, + device_type: BoostedHexDeviceType::All, + }, + BoostedHexInfo { + location: cell, + start_ts: None, + end_ts: None, + period_length: Duration::seconds(2592000), + multipliers: vec![NonZeroU32::new(3).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, + boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)?, + version: 0, + device_type: BoostedHexDeviceType::CbrsIndoor, + }, + // Expired boosts should not be considered + BoostedHexInfo { + location: cell, + start_ts: Some(now - Duration::days(60)), + end_ts: Some(now - Duration::days(30)), + period_length: Duration::seconds(2592000), + multipliers: vec![NonZeroU32::new(999).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, + boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)?, + version: 0, + device_type: BoostedHexDeviceType::All, + }, + ]; + + let boosted_hexes = BoostedHexes::new(hexes); + let boosts = boosted_hexes.get(&cell).expect("boosts for test cell"); + assert_eq!(boosts.len(), 3, "a hex can be boosted multiple times"); + + assert_eq!( + boosted_hexes.get_current_multiplier(cell, BoostedHexDeviceType::CbrsIndoor, now), + NonZeroU32::new(5), + "Specific boosts stack with ::ALL" + ); + assert_eq!( + boosted_hexes.get_current_multiplier(cell, BoostedHexDeviceType::WifiIndoor, now), + NonZeroU32::new(2), + "Missing boosts still return ::ALL" + ); + + Ok(()) + } + #[test] fn boosted_hex_from_proto_valid_not_started() -> anyhow::Result<()> { let proto = BoostedHexInfoProto { From fef2d67589eff4967ef522de9de1e42b9eb69297 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 25 Apr 2024 18:10:17 -0700 Subject: [PATCH 05/15] Add device_type to BoostedHexInfo from db The device_type column is assumed to be a jsonb, because the device_type column for the `mobile_hotspot_infos` table is an enum, and a jsonb field. It's nullable, which will represent boosting a hex for ALL device types. Otherwise, a camelcase value mapping to the DeviceTypeV0 in helium-program-library. https://github.com/helium/helium-program-library/blob/master/programs/hexboosting/src/state.rs This is different from the snake_case values used with protobufs. --- mobile_config/src/boosted_hex_info.rs | 175 +++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 6 deletions(-) diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index ab64d2499..686277f59 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -7,6 +7,7 @@ use helium_proto::BoostedHexDeviceTypeV1 as BoostedHexDeviceTypeProto; use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto; use hextree::Cell; use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; use std::{collections::HashMap, convert::TryFrom, num::NonZeroU32}; pub type BoostedHexInfoStream = BoxStream<'static, BoostedHexInfo>; @@ -30,6 +31,7 @@ pub struct BoostedHexInfo { impl TryFrom for BoostedHexInfo { type Error = anyhow::Error; + fn try_from(v: BoostedHexInfoProto) -> anyhow::Result { let period_length = Duration::seconds(v.period_length as i64); let device_type = v.device_type(); @@ -158,6 +160,20 @@ impl TryFrom for BoostedHexDeviceType { } } +impl FromStr for BoostedHexDeviceType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "cbrsIndoor" => Ok(Self::CbrsIndoor), + "cbrsOutdoor" => Ok(Self::CbrsOutdoor), + "wifiIndoor" => Ok(Self::WifiIndoor), + "wifiOutdoor" => Ok(Self::WifiOutdoor), + unknown => anyhow::bail!("unknown device type value: {unknown}"), + } + } +} + #[derive(PartialEq, Debug, Clone)] pub struct BoostedHex { pub location: Cell, @@ -240,7 +256,7 @@ impl BoostedHexes { .iter() .filter(|info| info.matches_device_type(&device_type)) .flat_map(|info| info.current_multiplier(ts)) - .map(|x| x.get()) + .map(|non_zero| non_zero.get()) .sum::(); NonZeroU32::new(current_multiplier) @@ -277,6 +293,7 @@ pub(crate) mod db { use solana_sdk::pubkey::Pubkey; use sqlx::{PgExecutor, Row}; use std::num::NonZeroU32; + use std::ops::Deref; use std::str::FromStr; const GET_BOOSTED_HEX_INFO_SQL: &str = r#" @@ -287,7 +304,8 @@ pub(crate) mod db { hexes.boosts_by_period as multipliers, hexes.address as boosted_hex_pubkey, config.address as boost_config_pubkey, - hexes.version + hexes.version, + hexes.device_type from boosted_hexes hexes join boost_configs config on hexes.boost_config = config.address "#; @@ -301,7 +319,8 @@ pub(crate) mod db { hexes.boosts_by_period as multipliers, hexes.address as boosted_hex_pubkey, config.address as boost_config_pubkey, - hexes.version + hexes.version, + hexes.device_type from boosted_hexes hexes join boost_configs config on hexes.boost_config = config.address where hexes.refreshed_at > $1 @@ -350,10 +369,12 @@ pub(crate) mod db { let location = Cell::try_from(row.get::("location")) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let device_type = match row.get::, &str>("device_type") { + type MaybeJsonb = Option>; + let device_type_jsonb = row.get::("device_type"); + let device_type = match device_type_jsonb { None => super::BoostedHexDeviceType::All, - Some(val) => super::BoostedHexDeviceType::try_from(val) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?, + Some(val) => super::BoostedHexDeviceType::from_str(val.deref()) + .map_err(|e| sqlx::Error::Decode(e.into()))?, }; Ok(Self { @@ -392,6 +413,7 @@ mod tests { use super::*; use chrono::NaiveDateTime; use hextree::Cell; + use sqlx::PgPool; use std::str::FromStr; const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU"; @@ -561,6 +583,147 @@ mod tests { Ok(()) } + #[sqlx::test] + #[ignore = "for manual metadata db testing"] + async fn parse_boosted_hex_info_from_database(pool: PgPool) -> anyhow::Result<()> { + let boost_config_address = Pubkey::new_unique(); + let now = Utc::now(); + + // NOTE(mj): Table creation taken from a dump of the metadata db. + // device_type was added to boosted_hexes as a jsonb field because the + // mobile_hotspot_infos table has a device_type column that maps to an + // enum, and it is a nullable jsonb column. + const CREATE_BOOSTED_HEXES_TABLE: &str = r#" + CREATE TABLE + boosted_hexes ( + address character varying(255) NOT NULL PRIMARY KEY, + boost_config character varying(255) NULL, + location numeric NULL, + start_ts numeric NULL, + reserved numeric[] NULL, + bump_seed integer NULL, + boosts_by_period bytea NULL, + version integer NULL, + refreshed_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + device_type jsonb + ) + "#; + + const CREATE_BOOST_CONFIG_TABLE: &str = r#" + CREATE TABLE + boost_configs ( + address character varying(255) NOT NULL PRIMARY KEY, + price_oracle character varying(255) NULL, + payment_mint character varying(255) NULL, + sub_dao character varying(255) NULL, + rent_reclaim_authority character varying(255) NULL, + boost_price numeric NULL, + period_length integer NULL, + minimum_periods integer NULL, + bump_seed integer NULL, + start_authority character varying(255) NULL, + refreshed_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL + ) + "#; + + sqlx::query(CREATE_BOOSTED_HEXES_TABLE) + .execute(&pool) + .await?; + sqlx::query(CREATE_BOOST_CONFIG_TABLE) + .execute(&pool) + .await?; + + const INSERT_BOOST_CONFIG: &str = r#" + INSERT INTO boost_configs ( + "boost_price", "bump_seed", "minimum_periods", "period_length", "refreshed_at", + + -- pubkeys + "price_oracle", + "payment_mint", + "rent_reclaim_authority", + "start_authority", + "sub_dao", + + -- our values + "address", + "created_at" + ) + VALUES ( + '5000', 250, 6, 2592000, '2024-03-12 21:13:52.692+00', + + $1, $2, $3, $4, $5, $6, $7 + ) + "#; + + const INSERT_BOOSTED_HEX: &str = r#" + INSERT INTO boosted_hexes ( + "boosts_by_period", "bump_seed", "location", "refreshed_at", "reserved", "start_ts", "version", + + -- our values + "address", + "boost_config", + "created_at", + "device_type" + ) + VALUES ( + 'ZGRkZGRk', 1, '631798453297853439', '2024-03-12 21:13:53.773+00', '{0,0,0,0,0,0,0,0}', '1708304400', 1, + + $1, $2, $3, $4 + ) + "#; + + // Insert boost config that boosted hexes will point to. + sqlx::query(INSERT_BOOST_CONFIG) + .bind(Pubkey::new_unique().to_string()) // price_oracle + .bind(Pubkey::new_unique().to_string()) // payment_mint + .bind(Pubkey::new_unique().to_string()) // rent_reclaim_authority + .bind(Pubkey::new_unique().to_string()) // start_authority + .bind(Pubkey::new_unique().to_string()) // sub_dao + // -- + .bind(boost_config_address.to_string()) // address + .bind(now) // created_at + .execute(&pool) + .await?; + + // Legacy boosted hex with NULL device_type + sqlx::query(INSERT_BOOSTED_HEX) + .bind(Pubkey::new_unique().to_string()) // address + .bind(boost_config_address.to_string()) // boost_config + .bind(now) // created_at + .bind(None as Option) // device_type + .execute(&pool) + .await?; + + // Boosted hex with new device types + for device_type in &["cbrsIndoor", "cbrsOutdoor", "wifiIndoor", "wifiOutdoor"] { + sqlx::query(INSERT_BOOSTED_HEX) + .bind(Pubkey::new_unique().to_string()) // address + .bind(boost_config_address.to_string()) // boost_config + .bind(now) // created_at + .bind(serde_json::json!(device_type)) // device_type + .execute(&pool) + .await?; + } + + let count: i64 = sqlx::query_scalar("select count(*) from boosted_hexes") + .fetch_one(&pool) + .await?; + assert_eq!(5, count, "there should be 1 of each type of boosted hex"); + + let mut infos = super::db::all_info_stream(&pool); + let mut print_count = 0; + while let Some(_info) = infos.next().await { + // println!("info: {_info:?}"); + print_count += 1; + } + + assert_eq!(5, print_count, "not all rows were able to parse"); + + Ok(()) + } + fn parse_dt(dt: &str) -> DateTime { NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") .expect("unable_to_parse") From 4107d20f5c8e0776b8a49513c7474881c3dc1990 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 26 Apr 2024 12:32:35 -0700 Subject: [PATCH 06/15] Rename to clarify try_into target type With type hints enabled, `hex` shows up as a `BoostedHex` going into a function that expects `BoostedHex`. Renaming to `hex_proto` will hopefully clear up the need for the type casting. --- boost_manager/src/activator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boost_manager/src/activator.rs b/boost_manager/src/activator.rs index 7d8f3868a..1dbc9e981 100644 --- a/boost_manager/src/activator.rs +++ b/boost_manager/src/activator.rs @@ -114,9 +114,9 @@ where while let Some(msg) = reward_shares.try_next().await? { let share = MobileRewardShare::decode(msg)?; if let Some(MobileReward::RadioReward(r)) = share.reward { - for hex in r.boosted_hexes.into_iter() { - process_boosted_hex(txn, manifest_time, &boosted_hexes, &hex.try_into()?) - .await? + for hex_proto in r.boosted_hexes.into_iter() { + let boosted_hex = hex_proto.try_into()?; + process_boosted_hex(txn, manifest_time, &boosted_hexes, &boosted_hex).await? } } } From c9b07d8c473d30d1e00058b8dbe6c7e1198f1257 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 26 Apr 2024 13:28:17 -0700 Subject: [PATCH 07/15] Update helium-proto to hip-109 branch --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a45355a99..475d5bc6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,10 +70,10 @@ helium-lib = { git = "https://github.com/helium/helium-wallet-rs.git", branch = hextree = { git = "https://github.com/jaykickliter/HexTree", branch = "main", features = [ "disktree", ] } -helium-proto = { git = "https://github.com/helium/proto", branch = "master", features = [ +helium-proto = { git = "https://github.com/helium/proto", branch = "mj/hip-109-boost-by-device-type", features = [ "services", ] } -beacon = { git = "https://github.com/helium/proto", branch = "master" } +beacon = { git = "https://github.com/helium/proto", branch = "mj/hip-109-boost-by-device-type" } solana-client = "1.18" solana-sdk = "1.18" solana-program = "1.18" @@ -126,10 +126,10 @@ sqlx = { git = "https://github.com/helium/sqlx.git", rev = "92a2268f02e0cac6fccb # When attempting to test proto changes without needing to push a branch you can # patch the github url to point to your local proto repo. -# +# # Patching for beacon must point directly to the crate, it will not look in the # repo for sibling crates. -# +# # [patch.'https://github.com/helium/proto'] # helium-proto = { path = "../proto" } # beacon = { path = "../proto/beacon" } From 87fd3caf36e675f3ecc63a72391242d9a6cdffec Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Sat, 27 Apr 2024 08:52:52 -0700 Subject: [PATCH 08/15] Add test for accumulating hex boosts This test uses a single type of radio to reduce adding noise to rewards by factors other than hex boosting. The first pass of the test calculates rewards for 2 radios with no boosted hexes to prove they are in equal standing. The second pass boosts only the hex for single radio, but with multiple BoostedHexDeviceType's to ensure they accumulate. --- .../tests/integrations/watcher_tests.rs | 5 +- .../tests/integrations/hex_boosting.rs | 271 +++++++++++++++++- 2 files changed, 268 insertions(+), 8 deletions(-) diff --git a/boost_manager/tests/integrations/watcher_tests.rs b/boost_manager/tests/integrations/watcher_tests.rs index 78d180eca..a403efab7 100644 --- a/boost_manager/tests/integrations/watcher_tests.rs +++ b/boost_manager/tests/integrations/watcher_tests.rs @@ -2,10 +2,7 @@ use crate::common::{self, MockFileSinkReceiver, MockHexBoostingClient}; use boost_manager::watcher::{self, Watcher}; use chrono::{Duration as ChronoDuration, Duration, Utc}; use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto; -use mobile_config::{ - boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo, BoostedHexInfoStream}, - client::{hex_boosting_client::HexBoostingInfoResolver, ClientError}, -}; +use mobile_config::boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo}; use solana_sdk::pubkey::Pubkey; use sqlx::PgPool; use std::{num::NonZeroU32, str::FromStr}; diff --git a/mobile_verifier/tests/integrations/hex_boosting.rs b/mobile_verifier/tests/integrations/hex_boosting.rs index bfbe42343..724096200 100644 --- a/mobile_verifier/tests/integrations/hex_boosting.rs +++ b/mobile_verifier/tests/integrations/hex_boosting.rs @@ -14,10 +14,7 @@ use helium_proto::services::{ }, }; use hextree::Cell; -use mobile_config::{ - boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo, BoostedHexInfoStream}, - client::{hex_boosting_client::HexBoostingInfoResolver, ClientError}, -}; +use mobile_config::boosted_hex_info::{BoostedHexDeviceType, BoostedHexInfo}; use mobile_verifier::{ cell_type::CellType, coverage::CoverageObject, @@ -1312,6 +1309,163 @@ fn rounded(num: Decimal) -> u64 { num.to_u64().unwrap_or_default() } +#[sqlx::test] +async fn test_poc_boosted_hex_stack_multiplier(pool: PgPool) -> anyhow::Result<()> { + // This test seeds the database with 2 identically performing radios on + // neighboring hexes. Both are eligible for boosted rewards. + // + // In the first pass, no hexes are boosted, to ensure the radios are equal. + // In the second pass, 1 radio is boosted with 2 boosts. + // - legacy boost, BoostType::All + // - specific boost, BoostType::CbrsIndoor + // Rewards are checked to ensure the 2 boosts accumulate correctly. + + let (mobile_rewards_client, mut mobile_rewards) = common::create_file_sink(); + let (speedtest_avg_client, _speedtest_avg_server) = common::create_file_sink(); + + let now = Utc::now(); + let epoch = (now - ChronoDuration::hours(24))..now; + + let boosted_pubkey = PublicKeyBinary::from_str(HOTSPOT_1)?; + let unboosted_pubkey = PublicKeyBinary::from_str(HOTSPOT_2)?; + + let boosted_location = Cell::from_raw(0x8a1fb466d2dffff)?; + let unboosted_location = Cell::from_raw(0x8a1fb46622d7fff)?; + + let boostable_radios = vec![ + HexBoostableRadio::cbrs_indoor(boosted_pubkey, boosted_location), + HexBoostableRadio::cbrs_indoor(unboosted_pubkey, unboosted_location), + ]; + + let mut txn = pool.begin().await?; + for radio in boostable_radios { + radio.seed(epoch.start, &mut txn).await?; + } + txn.commit().await?; + update_assignments(&pool).await?; + + // First pass with no boosted hexes, radios perform the same + { + let hex_boosting_client = MockHexBoostingClient::new(vec![]); + let (_, poc_rewards) = tokio::join!( + rewarder::reward_poc_and_dc( + &pool, + &hex_boosting_client, + &mobile_rewards_client, + &speedtest_avg_client, + &epoch, + dec!(0.0001) + ), + async { + let one = mobile_rewards.receive_radio_reward().await; + let two = mobile_rewards.receive_radio_reward().await; + + mobile_rewards.assert_no_messages(); + + [one, two] + }, + ); + + let [radio_one, radio_two] = &poc_rewards; + assert_eq!( + radio_one.coverage_points, radio_two.coverage_points, + "radios perform equally unboosted" + ); + assert_eq!( + radio_one.poc_reward, radio_two.poc_reward, + "radios earn equally unboosted" + ); + + // Make sure there were no unallocated rewards + let total = poc_rewards.iter().map(|r| r.poc_reward).sum::(); + assert_eq!( + reward_shares::get_scheduled_tokens_for_poc(epoch.end - epoch.start) + .to_u64() + .unwrap(), + total, + "allocated equals scheduled output" + ); + } + + // Second pass with boosted hex, first radio will be setup to receive 10x boost + { + let base = BoostedHexInfo { + location: boosted_location, + start_ts: None, + end_ts: None, + period_length: Duration::days(30), + multipliers: vec![], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY)?, + version: 0, + device_type: BoostedHexDeviceType::All, + }; + + let boosted_hexes = vec![ + BoostedHexInfo { + multipliers: vec![NonZeroU32::new(3).unwrap()], + device_type: BoostedHexDeviceType::All, + ..base.clone() + }, + BoostedHexInfo { + multipliers: vec![NonZeroU32::new(7).unwrap()], + device_type: BoostedHexDeviceType::CbrsIndoor, + ..base.clone() + }, + ]; + + let hex_boosting_client = MockHexBoostingClient::new(boosted_hexes); + let (_, mut poc_rewards) = tokio::join!( + rewarder::reward_poc_and_dc( + &pool, + &hex_boosting_client, + &mobile_rewards_client, + &speedtest_avg_client, + &epoch, + dec!(0.0001) + ), + async move { + let one = mobile_rewards.receive_radio_reward().await; + let two = mobile_rewards.receive_radio_reward().await; + + mobile_rewards.assert_no_messages(); + + [one, two] + }, + ); + + // sort by rewards ascending + poc_rewards.sort_by_key(|r| r.poc_reward); + let [unboosted, boosted] = &poc_rewards; + + let boosted_hex = boosted.boosted_hexes.first().expect("boosted hex"); + assert_eq!(10, boosted_hex.multiplier); + + assert_eq!( + 10, + boosted.coverage_points / unboosted.coverage_points, + "boosted radio should have 10x coverage_points" + ); + assert_eq!( + 10, + boosted.poc_reward / unboosted.poc_reward, + "boosted radio should have 10x poc_rewards" + ); + + // Make sure there were no unallocated rewards + let total = poc_rewards.iter().map(|r| r.poc_reward).sum::(); + assert_eq!( + reward_shares::get_scheduled_tokens_for_poc(epoch.end - epoch.start) + .to_u64() + .unwrap(), + total, + "allocated equals scheduled output" + ); + } + + Ok(()) +} + async fn receive_expected_rewards( mobile_rewards: &mut MockFileSinkReceiver, ) -> anyhow::Result<(Vec, UnallocatedReward)> { @@ -1924,3 +2078,112 @@ fn get_poc_allocation_buckets(epoch_duration: Duration) -> (Decimal, Decimal) { (regular_poc, boosted_poc) } + +/// Hex Boostable Radio Checklist: +/// - Coverage Object: 1 +/// - Seniority Update: 1 +/// - Heartbeats: 12 +/// - Speedtests: 2 +/// - Radio Threshold: 1 +struct HexBoostableRadio { + pubkey: PublicKeyBinary, + cbsd_id: Option, + hb_type: HbType, + is_indoor: bool, + location: Cell, + heartbeat_count: i64, +} + +impl HexBoostableRadio { + fn cbrs_indoor(pubkey: PublicKeyBinary, location: Cell) -> Self { + Self::new(pubkey, BoostedHexDeviceType::CbrsIndoor, location) + } + + fn new(pubkey: PublicKeyBinary, boost_type: BoostedHexDeviceType, location: Cell) -> Self { + let (hb_type, is_indoor, cbsd_id) = match boost_type { + BoostedHexDeviceType::All => panic!("a radio cannot be all types at once"), + BoostedHexDeviceType::CbrsIndoor => { + (HbType::Cbrs, true, Some(format!("cbsd-indoor-{pubkey}"))) + } + BoostedHexDeviceType::CbrsOutdoor => { + (HbType::Cbrs, false, Some(format!("cbsd-outdoor-{pubkey}"))) + } + BoostedHexDeviceType::WifiIndoor => (HbType::Wifi, true, None), + BoostedHexDeviceType::WifiOutdoor => (HbType::Wifi, false, None), + }; + Self { + pubkey, + cbsd_id, + hb_type, + is_indoor, + location, + heartbeat_count: 12, + } + } + + async fn seed( + self, + timestamp: DateTime, + txn: &mut Transaction<'_, Postgres>, + ) -> anyhow::Result<()> { + let cov_obj = create_coverage_object( + timestamp, + self.cbsd_id.clone(), + self.pubkey.clone(), + self.location.into_raw(), + self.is_indoor, + ); + for n in 0..=self.heartbeat_count { + let time_ahead = timestamp + ChronoDuration::hours(n); + let time_behind = timestamp - ChronoDuration::hours(24); + + let wifi_heartbeat = ValidatedHeartbeat { + heartbeat: Heartbeat { + hb_type: self.hb_type, + hotspot_key: self.pubkey.clone(), + cbsd_id: self.cbsd_id.clone(), + operation_mode: true, + lat: 0.0, + lon: 0.0, + coverage_object: Some(cov_obj.coverage_object.uuid), + location_validation_timestamp: Some(time_behind), + timestamp: time_ahead, + }, + cell_type: CellType::NovaGenericWifiIndoor, + distance_to_asserted: Some(10), + coverage_meta: None, + location_trust_score_multiplier: dec!(1.0), + validity: HeartbeatValidity::Valid, + }; + + let speedtest = CellSpeedtest { + pubkey: self.pubkey.clone(), + serial: format!("serial-{}", self.pubkey), + timestamp: timestamp - ChronoDuration::hours(n * 4), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 49, + }; + + save_seniority_object(time_ahead, &wifi_heartbeat, txn).await?; + wifi_heartbeat.save(txn).await?; + speedtests::save_speedtest(&speedtest, txn).await?; + } + cov_obj.save(txn).await?; + + let report = RadioThresholdIngestReport { + received_timestamp: Default::default(), + report: RadioThresholdReportReq { + hotspot_pubkey: self.pubkey.clone(), + cbsd_id: self.cbsd_id.clone(), + bytes_threshold: 1_000_000, + subscriber_threshold: 3, + threshold_timestamp: timestamp, + carrier_pub_key: CARRIER_HOTSPOT_KEY.parse().unwrap(), + }, + }; + radio_threshold::save(&report, txn).await?; + + Ok(()) + } +} From e926d4343ab06f44b43dd47414888427c24287d5 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Tue, 7 May 2024 14:01:56 -0700 Subject: [PATCH 09/15] use BoostedHexDeviceType proto enum There was not enough custom functionality to warrant owning a BoostedHexDeviceType enum of our own. --- mobile_config/src/boosted_hex_info.rs | 78 +++++---------------------- 1 file changed, 13 insertions(+), 65 deletions(-) diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index 686277f59..0b6436545 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -3,13 +3,12 @@ use chrono::{DateTime, Duration, Utc}; use file_store::traits::TimestampDecode; use futures::stream::{BoxStream, StreamExt}; use helium_proto::services::poc_mobile::BoostedHex as BoostedHexProto; -use helium_proto::BoostedHexDeviceTypeV1 as BoostedHexDeviceTypeProto; use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto; use hextree::Cell; use solana_sdk::pubkey::Pubkey; -use std::str::FromStr; use std::{collections::HashMap, convert::TryFrom, num::NonZeroU32}; +pub use helium_proto::BoostedHexDeviceTypeV1 as BoostedHexDeviceType; pub type BoostedHexInfoStream = BoxStream<'static, BoostedHexInfo>; lazy_static::lazy_static! { @@ -54,7 +53,7 @@ impl TryFrom for BoostedHexInfo { boosted_hex_pubkey, boost_config_pubkey, version: v.version, - device_type: device_type.into(), + device_type, }) } } @@ -113,64 +112,13 @@ impl BoostedHexInfo { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BoostedHexDeviceType { - All, - CbrsIndoor, - CbrsOutdoor, - WifiIndoor, - WifiOutdoor, -} - -impl From for BoostedHexDeviceType { - fn from(device_type_proto: BoostedHexDeviceTypeProto) -> Self { - match device_type_proto { - BoostedHexDeviceTypeProto::All => Self::All, - BoostedHexDeviceTypeProto::CbrsIndoor => Self::CbrsIndoor, - BoostedHexDeviceTypeProto::CbrsOutdoor => Self::CbrsOutdoor, - BoostedHexDeviceTypeProto::WifiIndoor => Self::WifiIndoor, - BoostedHexDeviceTypeProto::WifiOutdoor => Self::WifiOutdoor, - } - } -} - -impl From for BoostedHexDeviceTypeProto { - fn from(device_type: BoostedHexDeviceType) -> Self { - match device_type { - BoostedHexDeviceType::All => Self::All, - BoostedHexDeviceType::CbrsIndoor => Self::CbrsIndoor, - BoostedHexDeviceType::CbrsOutdoor => Self::CbrsOutdoor, - BoostedHexDeviceType::WifiIndoor => Self::WifiIndoor, - BoostedHexDeviceType::WifiOutdoor => Self::WifiOutdoor, - } - } -} - -impl From for i32 { - fn from(device_type: BoostedHexDeviceType) -> Self { - BoostedHexDeviceTypeProto::from(device_type) as i32 - } -} - -impl TryFrom for BoostedHexDeviceType { - type Error = helium_proto::DecodeError; - - fn try_from(db_value: i32) -> Result { - Ok(BoostedHexDeviceTypeProto::try_from(db_value)?.into()) - } -} - -impl FromStr for BoostedHexDeviceType { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s { - "cbrsIndoor" => Ok(Self::CbrsIndoor), - "cbrsOutdoor" => Ok(Self::CbrsOutdoor), - "wifiIndoor" => Ok(Self::WifiIndoor), - "wifiOutdoor" => Ok(Self::WifiOutdoor), - unknown => anyhow::bail!("unknown device type value: {unknown}"), - } +fn device_type_from_str(s: &str) -> anyhow::Result { + match s { + "cbrsIndoor" => Ok(BoostedHexDeviceType::CbrsIndoor), + "cbrsOutdoor" => Ok(BoostedHexDeviceType::CbrsOutdoor), + "wifiIndoor" => Ok(BoostedHexDeviceType::WifiIndoor), + "wifiOutdoor" => Ok(BoostedHexDeviceType::WifiOutdoor), + unknown => anyhow::bail!("unknown device type value: {unknown}"), } } @@ -373,7 +321,7 @@ pub(crate) mod db { let device_type_jsonb = row.get::("device_type"); let device_type = match device_type_jsonb { None => super::BoostedHexDeviceType::All, - Some(val) => super::BoostedHexDeviceType::from_str(val.deref()) + Some(val) => super::device_type_from_str(val.deref()) .map_err(|e| sqlx::Error::Decode(e.into()))?, }; @@ -496,7 +444,7 @@ mod tests { .to_bytes() .to_vec(), version: 1, - device_type: BoostedHexDeviceTypeProto::All.into(), + device_type: BoostedHexDeviceType::All.into(), }; let msg = BoostedHexInfo::try_from(proto)?; @@ -538,7 +486,7 @@ mod tests { .to_bytes() .to_vec(), version: 1, - device_type: BoostedHexDeviceTypeProto::All.into(), + device_type: BoostedHexDeviceType::All.into(), }; let msg = BoostedHexInfo::try_from(proto)?; @@ -574,7 +522,7 @@ mod tests { boosted_hex_pubkey: BOOST_HEX_PUBKEY.as_bytes().to_vec(), boost_config_pubkey: BOOST_HEX_CONFIG_PUBKEY.as_bytes().to_vec(), version: 1, - device_type: BoostedHexDeviceTypeProto::All.into(), + device_type: BoostedHexDeviceType::All.into(), }; assert_eq!( "multipliers cannot contain values of 0", From 8dea91e38e189678ee19ed95d335706579fd24d6 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 8 May 2024 10:21:45 -0700 Subject: [PATCH 10/15] Mobile Config filter Boosted Hexes (#801) * Refactor to use BoostedHexes methods Making the internal member hexes private will make it easier to change the implementation when device type is introduced. * Add device type to boosted hex info * refactor metadata_db tests to make test clearer also makes it easier to add new tests * remove expired boosted hexes when streaming from db * ensure no tests are written with expired boosted hexes * optimize by computing end_ts in db query Thanks for the query help Brian! By precomputing the end timestamp of a boosted hex, we can not have to stream all the hexes out of the db just to throw them away. * fixup after rebase - remove unused imports - remove old refactor return types * make boosted hex test function more explicit If the expired check had been made a global check, the ability to use BoostedHexes for modified hexes would have broken at runtime. The attempt here is to make very explicit during testing how to meet the same contract as the database queries for boosted hexes. I think there are still some cracks, but we can narrow in on those as we find them. For now, I think naming test constructor functions is a good start. --- boost_manager/src/activator.rs | 2 +- .../tests/integrations/activator_tests.rs | 6 +- mobile_config/src/boosted_hex_info.rs | 432 +++++++++++------- mobile_config/src/hex_boosting_service.rs | 2 +- mobile_verifier/src/rewarder.rs | 2 +- .../tests/integrations/modeled_coverage.rs | 71 +-- 6 files changed, 310 insertions(+), 205 deletions(-) diff --git a/boost_manager/src/activator.rs b/boost_manager/src/activator.rs index 1dbc9e981..d51586e80 100644 --- a/boost_manager/src/activator.rs +++ b/boost_manager/src/activator.rs @@ -96,7 +96,7 @@ where manifest: RewardManifest, ) -> Result<()> { // get latest boosted hexes info from mobile config - let boosted_hexes = BoostedHexes::get_all(&self.hex_boosting_client).await?; + let boosted_hexes = BoostedHexes::get_active(&self.hex_boosting_client).await?; // get the rewards file from the manifest let manifest_time = manifest.end_timestamp; diff --git a/boost_manager/tests/integrations/activator_tests.rs b/boost_manager/tests/integrations/activator_tests.rs index 0212fcc26..b59ff4e4d 100644 --- a/boost_manager/tests/integrations/activator_tests.rs +++ b/boost_manager/tests/integrations/activator_tests.rs @@ -87,7 +87,7 @@ impl TestContext { async fn test_activated_hex_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now(); let ctx = TestContext::setup(now)?; - let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); + let boosted_hexes = BoostedHexes::test_new_active(ctx.boosted_hexes)?; // test a boosted hex derived from radio rewards // with a non set start date, will result in a row being @@ -117,7 +117,7 @@ async fn test_activated_hex_insert(pool: PgPool) -> anyhow::Result<()> { async fn test_activated_hex_no_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now(); let ctx = TestContext::setup(now)?; - let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); + let boosted_hexes = BoostedHexes::test_new_active(ctx.boosted_hexes)?; // test a boosted hex derived from radio rewards // with an active start date, will result in no row being @@ -143,7 +143,7 @@ async fn test_activated_hex_no_insert(pool: PgPool) -> anyhow::Result<()> { async fn test_activated_dup_hex_insert(pool: PgPool) -> anyhow::Result<()> { let now = Utc::now().with_second(0).unwrap(); let ctx = TestContext::setup(now)?; - let boosted_hexes = BoostedHexes::new(ctx.boosted_hexes); + let boosted_hexes = BoostedHexes::test_new_active(ctx.boosted_hexes)?; // test with DUPLICATE boosted hexes derived from radio rewards // with a non set start date, will result in a single row being diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index 0b6436545..1601b0a85 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -40,7 +40,7 @@ impl TryFrom for BoostedHexInfo { .map(NonZeroU32::new) .collect::>>() .ok_or_else(|| anyhow::anyhow!("multipliers cannot contain values of 0"))?; - let start_ts = to_start_ts(v.start_ts); + let start_ts = to_timestamp(v.start_ts); let end_ts = to_end_ts(start_ts, period_length, multipliers.len()); let boosted_hex_pubkey: Pubkey = Pubkey::try_from(v.boosted_hex_pubkey.as_slice())?; let boost_config_pubkey: Pubkey = Pubkey::try_from(v.boost_config_pubkey.as_slice())?; @@ -149,15 +149,21 @@ pub struct BoostedHexes { } impl BoostedHexes { - pub fn new(hexes: Vec) -> Self { + pub fn test_new_active(hexes: Vec) -> anyhow::Result { let mut me = Self::default(); for hex in hexes { + if hex.end_ts.is_some_and(|end| end < Utc::now()) { + // mobile-config does not deliver expired boosts from the database. + // Tests using this struct to mimic mobile-config should uphold the + // same contract. + panic!("Active BoostedHexes should not contain expired boosts"); + } me.insert(hex); } - me + Ok(me) } - pub async fn get_all( + pub async fn get_active( hex_service_client: &impl HexBoostingInfoResolver, ) -> anyhow::Result { let mut stream = hex_service_client @@ -222,7 +228,7 @@ impl BoostedHexes { self.hexes.get(location) } - pub fn insert(&mut self, info: BoostedHexInfo) { + fn insert(&mut self, info: BoostedHexInfo) { self.hexes.entry(info.location).or_default().push(info); } } @@ -234,7 +240,8 @@ impl coverage_map::BoostedHexMap for BoostedHexes { } pub(crate) mod db { - use super::{to_end_ts, to_start_ts, BoostedHexInfo}; + + use super::{to_timestamp, BoostedHexInfo}; use chrono::{DateTime, Duration, Utc}; use futures::stream::{Stream, StreamExt}; use hextree::Cell; @@ -245,39 +252,71 @@ pub(crate) mod db { use std::str::FromStr; const GET_BOOSTED_HEX_INFO_SQL: &str = r#" - select - CAST(hexes.location as bigint), - CAST(hexes.start_ts as bigint), + WITH boosted_hexes_replacement AS ( + SELECT + h.*, + CASE + WHEN start_ts = 0 THEN 0 + ELSE h.start_ts + (c.period_length * length(h.boosts_by_period)) + END AS end_ts + FROM boost_configs c + INNER JOIN boosted_hexes h ON c.address = h.boost_config + ) + SELECT + CAST(hexes.location as bigint), + CAST(hexes.start_ts as bigint), + CAST(hexes.end_ts as bigint), config.period_length, hexes.boosts_by_period as multipliers, - hexes.address as boosted_hex_pubkey, + hexes.address as boosted_hex_pubkey, config.address as boost_config_pubkey, hexes.version, hexes.device_type - from boosted_hexes hexes + from boosted_hexes_replacement hexes join boost_configs config on hexes.boost_config = config.address + WHERE + hexes.start_ts = 0 + OR hexes.end_ts > date_part('epoch', $1) "#; - // TODO: reuse with string above + // NOTE(mj): modified hexes should be returned regardless of expiration status const GET_MODIFIED_BOOSTED_HEX_INFO_SQL: &str = r#" - select - CAST(hexes.location as bigint), - CAST(hexes.start_ts as bigint), + WITH boosted_hexes_replacement AS ( + SELECT + h.*, + CASE + WHEN start_ts = 0 THEN 0 + ELSE h.start_ts + (c.period_length * length(h.boosts_by_period)) + END AS end_ts + FROM boost_configs c + INNER JOIN boosted_hexes h ON c.address = h.boost_config + ) + SELECT + CAST(hexes.location AS bigint), + CAST(hexes.start_ts AS bigint), config.period_length, - hexes.boosts_by_period as multipliers, - hexes.address as boosted_hex_pubkey, - config.address as boost_config_pubkey, + hexes.boosts_by_period AS multipliers, + hexes.address AS boosted_hex_pubkey, + config.address AS boost_config_pubkey, hexes.version, hexes.device_type - from boosted_hexes hexes - join boost_configs config on hexes.boost_config = config.address - where hexes.refreshed_at > $1 + FROM boosted_hexes_replacement hexes + JOIN boost_configs config ON hexes.boost_config = config.address + WHERE hexes.refreshed_at > $1 "#; - pub fn all_info_stream<'a>( + pub fn all_info_stream_with_time_now<'a>( + db: impl PgExecutor<'a> + 'a, + ) -> impl Stream + 'a { + all_info_stream(db, Utc::now()) + } + + fn all_info_stream<'a>( db: impl PgExecutor<'a> + 'a, + now: DateTime, ) -> impl Stream + 'a { sqlx::query_as::<_, BoostedHexInfo>(GET_BOOSTED_HEX_INFO_SQL) + .bind(now) .fetch(db) .filter_map(|info| async move { info.ok() }) .boxed() @@ -297,7 +336,7 @@ pub(crate) mod db { impl sqlx::FromRow<'_, sqlx::postgres::PgRow> for BoostedHexInfo { fn from_row(row: &sqlx::postgres::PgRow) -> sqlx::Result { let period_length = Duration::seconds(row.get::("period_length") as i64); - let start_ts = to_start_ts(row.get::("start_ts") as u64); + let start_ts = to_timestamp(row.get::("start_ts") as u64); let multipliers = row .get::, &str>("multipliers") .into_iter() @@ -306,7 +345,7 @@ pub(crate) mod db { .ok_or_else(|| { sqlx::Error::Decode(Box::from("multipliers cannot contain values of 0")) })?; - let end_ts = to_end_ts(start_ts, period_length, multipliers.len()); + let end_ts = to_timestamp(row.get::("end_ts") as u64); let boost_config_pubkey = Pubkey::from_str(row.get::<&str, &str>("boost_config_pubkey")) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; @@ -340,7 +379,7 @@ pub(crate) mod db { } } -fn to_start_ts(timestamp: u64) -> Option> { +fn to_timestamp(timestamp: u64) -> Option> { if timestamp == 0 { None } else { @@ -395,23 +434,11 @@ mod tests { version: 0, device_type: BoostedHexDeviceType::CbrsIndoor, }, - // Expired boosts should not be considered - BoostedHexInfo { - location: cell, - start_ts: Some(now - Duration::days(60)), - end_ts: Some(now - Duration::days(30)), - period_length: Duration::seconds(2592000), - multipliers: vec![NonZeroU32::new(999).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, - boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)?, - version: 0, - device_type: BoostedHexDeviceType::All, - }, ]; - let boosted_hexes = BoostedHexes::new(hexes); + let boosted_hexes = BoostedHexes::test_new_active(hexes)?; let boosts = boosted_hexes.get(&cell).expect("boosts for test cell"); - assert_eq!(boosts.len(), 3, "a hex can be boosted multiple times"); + assert_eq!(boosts.len(), 2, "a hex can be boosted multiple times"); assert_eq!( boosted_hexes.get_current_multiplier(cell, BoostedHexDeviceType::CbrsIndoor, now), @@ -531,150 +558,227 @@ mod tests { Ok(()) } - #[sqlx::test] - #[ignore = "for manual metadata db testing"] - async fn parse_boosted_hex_info_from_database(pool: PgPool) -> anyhow::Result<()> { - let boost_config_address = Pubkey::new_unique(); - let now = Utc::now(); + fn parse_dt(dt: &str) -> DateTime { + NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") + .expect("unable_to_parse") + .and_utc() + } - // NOTE(mj): Table creation taken from a dump of the metadata db. - // device_type was added to boosted_hexes as a jsonb field because the - // mobile_hotspot_infos table has a device_type column that maps to an - // enum, and it is a nullable jsonb column. - const CREATE_BOOSTED_HEXES_TABLE: &str = r#" - CREATE TABLE - boosted_hexes ( - address character varying(255) NOT NULL PRIMARY KEY, - boost_config character varying(255) NULL, - location numeric NULL, - start_ts numeric NULL, - reserved numeric[] NULL, - bump_seed integer NULL, - boosts_by_period bytea NULL, - version integer NULL, - refreshed_at timestamp with time zone NULL, - created_at timestamp with time zone NOT NULL, - device_type jsonb - ) - "#; + mod metadata_db { + /// Table creation was taken from a dump of the metadata db. + /// `device_type` was added to boosted_hexes as a jsonb field because the + /// mobile_hotspot_infos table has a device_type column that maps to an + /// enum, and it is a nullable jsonb column. + /// + /// When the `boosted_hexes` or `boost_configs` tables from the metadata_db + /// are updated, these tests will not break until the new table formats are + /// put into `create_tables()`. + use super::*; + + #[sqlx::test] + async fn parse_boosted_hex_info_from_database(pool: PgPool) -> anyhow::Result<()> { + let boost_config_address = Pubkey::new_unique(); + let now = Utc::now(); + + create_tables(&pool).await?; + insert_boost_config(&pool, &boost_config_address, now).await?; + + let device_types = vec![ + None, // legacy boosted hex with NULL device_type + Some(serde_json::json!("cbrsIndoor")), + Some(serde_json::json!("cbrsOutdoor")), + Some(serde_json::json!("wifiIndoor")), + Some(serde_json::json!("wifiOutdoor")), + // Some(serde_json::json!(null)) // this is different from None, and will/should break parsing as it's not part of the enum + ]; + for device_type in device_types { + insert_boosted_hex(&pool, &boost_config_address, now, device_type, Some(now)) + .await?; + } + + assert_eq!( + 5, + boosted_hexes_count(&pool).await?, + "there should be 1 of each type of boosted hex" + ); + assert_eq!( + 5, + streamed_hexes_count(&pool).await?, + "not all rows were able to parse" + ); + + Ok(()) + } - const CREATE_BOOST_CONFIG_TABLE: &str = r#" - CREATE TABLE - boost_configs ( - address character varying(255) NOT NULL PRIMARY KEY, - price_oracle character varying(255) NULL, - payment_mint character varying(255) NULL, - sub_dao character varying(255) NULL, - rent_reclaim_authority character varying(255) NULL, - boost_price numeric NULL, - period_length integer NULL, - minimum_periods integer NULL, - bump_seed integer NULL, - start_authority character varying(255) NULL, - refreshed_at timestamp with time zone NULL, - created_at timestamp with time zone NOT NULL + #[sqlx::test] + async fn filter_expired_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { + let boost_config_address = Pubkey::new_unique(); + let now = Utc::now(); + + create_tables(&pool).await?; + insert_boost_config(&pool, &boost_config_address, now).await?; + + let times = vec![ + None, // unstarted + Some(now), // still boosting + Some(now - Duration::days(400)), // expired + ]; + + for time in times { + insert_boosted_hex( + &pool, + &boost_config_address, + now, + Some(serde_json::json!("cbrsIndoor")), + time, ) - "#; - - sqlx::query(CREATE_BOOSTED_HEXES_TABLE) - .execute(&pool) - .await?; - sqlx::query(CREATE_BOOST_CONFIG_TABLE) - .execute(&pool) - .await?; - - const INSERT_BOOST_CONFIG: &str = r#" - INSERT INTO boost_configs ( - "boost_price", "bump_seed", "minimum_periods", "period_length", "refreshed_at", + .await?; + } - -- pubkeys - "price_oracle", - "payment_mint", - "rent_reclaim_authority", - "start_authority", - "sub_dao", + assert_eq!(3, boosted_hexes_count(&pool).await?); + assert_eq!(2, streamed_hexes_count(&pool).await?); - -- our values - "address", - "created_at" - ) - VALUES ( - '5000', 250, 6, 2592000, '2024-03-12 21:13:52.692+00', + Ok(()) + } - $1, $2, $3, $4, $5, $6, $7 - ) - "#; + async fn create_tables(pool: &PgPool) -> anyhow::Result<()> { + const CREATE_BOOSTED_HEXES_TABLE: &str = r#" + CREATE TABLE + boosted_hexes ( + address character varying(255) NOT NULL PRIMARY KEY, + boost_config character varying(255) NULL, + location numeric NULL, + start_ts numeric NULL, + reserved numeric[] NULL, + bump_seed integer NULL, + boosts_by_period bytea NULL, + version integer NULL, + refreshed_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL, + device_type jsonb + ) + "#; + + const CREATE_BOOST_CONFIG_TABLE: &str = r#" + CREATE TABLE + boost_configs ( + address character varying(255) NOT NULL PRIMARY KEY, + price_oracle character varying(255) NULL, + payment_mint character varying(255) NULL, + sub_dao character varying(255) NULL, + rent_reclaim_authority character varying(255) NULL, + boost_price numeric NULL, + period_length integer NULL, + minimum_periods integer NULL, + bump_seed integer NULL, + start_authority character varying(255) NULL, + refreshed_at timestamp with time zone NULL, + created_at timestamp with time zone NOT NULL + ) + "#; + + sqlx::query(CREATE_BOOSTED_HEXES_TABLE) + .execute(pool) + .await?; + sqlx::query(CREATE_BOOST_CONFIG_TABLE).execute(pool).await?; + Ok(()) + } - const INSERT_BOOSTED_HEX: &str = r#" - INSERT INTO boosted_hexes ( - "boosts_by_period", "bump_seed", "location", "refreshed_at", "reserved", "start_ts", "version", + async fn insert_boost_config( + pool: &PgPool, + boost_config_address: &Pubkey, + created_at: DateTime, + ) -> anyhow::Result<()> { + const INSERT_BOOST_CONFIG: &str = r#" + INSERT INTO boost_configs ( + "boost_price", "bump_seed", "minimum_periods", "period_length", "refreshed_at", + + -- pubkeys + "price_oracle", + "payment_mint", + "rent_reclaim_authority", + "start_authority", + "sub_dao", + + -- our values + "address", + "created_at" + ) + VALUES ( + '5000', 250, 6, 2592000, '2024-03-12 21:13:52.692+00', - -- our values - "address", - "boost_config", - "created_at", - "device_type" - ) - VALUES ( - 'ZGRkZGRk', 1, '631798453297853439', '2024-03-12 21:13:53.773+00', '{0,0,0,0,0,0,0,0}', '1708304400', 1, + $1, $2, $3, $4, $5, $6, $7 + ) + "#; + + sqlx::query(INSERT_BOOST_CONFIG) + .bind(Pubkey::new_unique().to_string()) // price_oracle + .bind(Pubkey::new_unique().to_string()) // payment_mint + .bind(Pubkey::new_unique().to_string()) // rent_reclaim_authority + .bind(Pubkey::new_unique().to_string()) // start_authority + .bind(Pubkey::new_unique().to_string()) // sub_dao + // -- + .bind(boost_config_address.to_string()) // address + .bind(created_at) // created_at + .execute(pool) + .await?; - $1, $2, $3, $4 - ) - "#; + Ok(()) + } - // Insert boost config that boosted hexes will point to. - sqlx::query(INSERT_BOOST_CONFIG) - .bind(Pubkey::new_unique().to_string()) // price_oracle - .bind(Pubkey::new_unique().to_string()) // payment_mint - .bind(Pubkey::new_unique().to_string()) // rent_reclaim_authority - .bind(Pubkey::new_unique().to_string()) // start_authority - .bind(Pubkey::new_unique().to_string()) // sub_dao - // -- - .bind(boost_config_address.to_string()) // address - .bind(now) // created_at - .execute(&pool) - .await?; + async fn insert_boosted_hex( + pool: &PgPool, + boost_config_address: &Pubkey, + created_at: DateTime, + device_type: Option, + start_ts: Option>, + ) -> anyhow::Result<()> { + const INSERT_BOOSTED_HEX: &str = r#" + INSERT INTO boosted_hexes ( + "boosts_by_period", "bump_seed", "location", "refreshed_at", "reserved", "version", + + -- our values + "address", + "boost_config", + "created_at", + "device_type", + "start_ts" + ) + VALUES ( + 'ZGRkZGRk', 1, '631798453297853439', '2024-03-12 21:13:53.773+00', '{0,0,0,0,0,0,0,0}', 1, - // Legacy boosted hex with NULL device_type - sqlx::query(INSERT_BOOSTED_HEX) - .bind(Pubkey::new_unique().to_string()) // address - .bind(boost_config_address.to_string()) // boost_config - .bind(now) // created_at - .bind(None as Option) // device_type - .execute(&pool) - .await?; + $1, $2, $3, $4, $5 + ) + "#; - // Boosted hex with new device types - for device_type in &["cbrsIndoor", "cbrsOutdoor", "wifiIndoor", "wifiOutdoor"] { sqlx::query(INSERT_BOOSTED_HEX) .bind(Pubkey::new_unique().to_string()) // address .bind(boost_config_address.to_string()) // boost_config - .bind(now) // created_at - .bind(serde_json::json!(device_type)) // device_type - .execute(&pool) + .bind(created_at) // created_at + .bind(device_type) // device_type + .bind(start_ts.map(|t| t.timestamp()).unwrap_or_default()) // start_ts + .execute(pool) .await?; - } - let count: i64 = sqlx::query_scalar("select count(*) from boosted_hexes") - .fetch_one(&pool) - .await?; - assert_eq!(5, count, "there should be 1 of each type of boosted hex"); - - let mut infos = super::db::all_info_stream(&pool); - let mut print_count = 0; - while let Some(_info) = infos.next().await { - // println!("info: {_info:?}"); - print_count += 1; + Ok(()) } - assert_eq!(5, print_count, "not all rows were able to parse"); - - Ok(()) - } + async fn boosted_hexes_count(pool: &PgPool) -> anyhow::Result { + let count: i64 = sqlx::query_scalar("select count(*) from boosted_hexes") + .fetch_one(pool) + .await?; + Ok(count) + } - fn parse_dt(dt: &str) -> DateTime { - NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") - .expect("unable_to_parse") - .and_utc() + async fn streamed_hexes_count(pool: &PgPool) -> anyhow::Result { + // If a row cannot be parsed, it is dropped from the stream with no erros. + let mut infos = super::db::all_info_stream_with_time_now(pool); + let mut count = 0; + while let Some(_info) = infos.next().await { + // println!("info: {_info:?}"); + count += 1; + } + Ok(count) + } } } diff --git a/mobile_config/src/hex_boosting_service.rs b/mobile_config/src/hex_boosting_service.rs index 08ed50f1c..82b6fc22d 100644 --- a/mobile_config/src/hex_boosting_service.rs +++ b/mobile_config/src/hex_boosting_service.rs @@ -71,7 +71,7 @@ impl mobile_config::HexBoosting for HexBoostingService { let (tx, rx) = tokio::sync::mpsc::channel(100); tokio::spawn(async move { - let stream = boosted_hex_info::db::all_info_stream(&pool); + let stream = boosted_hex_info::db::all_info_stream_with_time_now(&pool); stream_multi_info(stream, tx.clone(), signing_key.clone(), batch_size).await }); diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index 76cb12313..4fcd61e93 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -424,7 +424,7 @@ async fn reward_poc( speedtest_averages.write_all(speedtest_avg_sink).await?; - let boosted_hexes = BoostedHexes::get_all(hex_service_client).await?; + let boosted_hexes = BoostedHexes::get_active(hex_service_client).await?; let boosted_hex_eligibility = BoostedHexEligibility::new( radio_threshold::verified_radio_thresholds(pool, reward_period).await?, diff --git a/mobile_verifier/tests/integrations/modeled_coverage.rs b/mobile_verifier/tests/integrations/modeled_coverage.rs index c4f846fd7..825523282 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -840,41 +840,42 @@ async fn scenario_three(pool: PgPool) -> anyhow::Result<()> { averages.insert(owner_6.clone(), SpeedtestAverage::from(speedtests_6)); let speedtest_avgs = SpeedtestAverages { averages }; - let mut boosted_hexes = BoostedHexes::default(); - boosted_hexes.insert(BoostedHexInfo { - location: Cell::from_raw(0x8a1fb466d2dffff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(1).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - device_type: BoostedHexDeviceType::All, - }); - boosted_hexes.insert(BoostedHexInfo { - location: Cell::from_raw(0x8a1fb49642dffff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(2).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - device_type: BoostedHexDeviceType::All, - }); - boosted_hexes.insert(BoostedHexInfo { - // hotspot 1's location - location: Cell::from_raw(0x8c2681a306607ff)?, - start_ts: None, - end_ts: None, - period_length: Duration::hours(1), - multipliers: vec![NonZeroU32::new(3).unwrap()], - boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), - boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), - version: 0, - device_type: BoostedHexDeviceType::All, - }); + let boosted_hexes = BoostedHexes::test_new_active(vec![ + BoostedHexInfo { + location: Cell::from_raw(0x8a1fb466d2dffff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(1).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + device_type: BoostedHexDeviceType::All, + }, + BoostedHexInfo { + location: Cell::from_raw(0x8a1fb49642dffff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(2).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + device_type: BoostedHexDeviceType::All, + }, + BoostedHexInfo { + // hotspot 1's location + location: Cell::from_raw(0x8c2681a306607ff)?, + start_ts: None, + end_ts: None, + period_length: Duration::hours(1), + multipliers: vec![NonZeroU32::new(3).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), + version: 0, + device_type: BoostedHexDeviceType::All, + }, + ])?; let reward_period = start..end; let heartbeats = HeartbeatReward::validated(&pool, &reward_period); From bbb1209173141c71f17bdf077a8357ef9153ba8d Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 10 May 2024 18:12:11 -0700 Subject: [PATCH 11/15] add test to ensure boosts only apply to single device types There is another test for same device types across hexes. This test is for different types in the same hex. The 2nd unboosted hex serves the purpose of giving us a relative baseline so we don't need to hardcode values into the test. --- .../tests/integrations/hex_boosting.rs | 103 +++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/mobile_verifier/tests/integrations/hex_boosting.rs b/mobile_verifier/tests/integrations/hex_boosting.rs index 724096200..76b62361a 100644 --- a/mobile_verifier/tests/integrations/hex_boosting.rs +++ b/mobile_verifier/tests/integrations/hex_boosting.rs @@ -25,7 +25,7 @@ use rust_decimal::prelude::*; use rust_decimal_macros::dec; use solana_sdk::pubkey::Pubkey; use sqlx::{PgPool, Postgres, Transaction}; -use std::{num::NonZeroU32, str::FromStr}; +use std::{collections::HashMap, num::NonZeroU32, str::FromStr}; use uuid::Uuid; const HOTSPOT_1: &str = "112E7TxoNHV46M6tiPA8N1MkeMeQxc9ztb4JQLXBVAAUfq1kJLoF"; @@ -1466,6 +1466,99 @@ async fn test_poc_boosted_hex_stack_multiplier(pool: PgPool) -> anyhow::Result<( Ok(()) } +#[sqlx::test] +async fn test_poc_boosted_hex_only_applies_to_device_type(pool: PgPool) -> anyhow::Result<()> { + // This tests ensures that device specific device types don't affect other device types. + // There are 2 hexes: + // - #1 BoostType::CbrsOutdoor at 5x + // - #2 No Boosts + // + // There are 4 radios that all qualify for hex boosting: + // - CbrsOutdoor in boosted hex + // - Cbrsoutdoor in unboosted hex + // - WifiOutdoor in boosted hex + // - WifiOutdoor in unboosted hex + // + // The boosted cbrs radio should have 5x the rewards of the unboosted cbrs radio. + // The wifi radios should have the same rewards. + // + + let (mobile_rewards_client, mut mobile_rewards) = common::create_file_sink(); + let (speedtest_avg_client, _speedtest_avg_server) = common::create_file_sink(); + + let now = Utc::now(); + let epoch = (now - ChronoDuration::hours(24))..now; + + let boosted_cbrs_pubkey = PublicKeyBinary::from_str(HOTSPOT_1)?; + let unboosted_cbrs_pubkey = PublicKeyBinary::from_str(HOTSPOT_2)?; + let wifi_pubkey1 = PublicKeyBinary::from_str(HOTSPOT_3)?; + let wifi_pubkey2 = PublicKeyBinary::from_str(HOTSPOT_4)?; + + let boosted_location = Cell::from_raw(0x8a1fb466d2dffff)?; + let unboosted_location = Cell::from_raw(0x8a1fb46622d7fff)?; + + let boostable_radios = vec![ + HexBoostableRadio::cbrs_outdoor(boosted_cbrs_pubkey.clone(), boosted_location), + HexBoostableRadio::cbrs_outdoor(unboosted_cbrs_pubkey.clone(), unboosted_location), + HexBoostableRadio::wifi_outdoor(wifi_pubkey1.clone(), boosted_location), + HexBoostableRadio::wifi_outdoor(wifi_pubkey2.clone(), unboosted_location), + ]; + + let mut txn = pool.begin().await?; + for radio in boostable_radios { + radio.seed(epoch.start, &mut txn).await?; + } + txn.commit().await?; + update_assignments(&pool).await?; + + let boosted_hex = BoostedHexInfo { + location: boosted_location, + start_ts: None, + end_ts: None, + period_length: Duration::days(30), + multipliers: vec![NonZeroU32::new(5).unwrap()], + boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)?, + boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY)?, + version: 0, + device_type: BoostedHexDeviceType::CbrsOutdoor, + }; + + let hex_boosting_client = MockHexBoostingClient::new(vec![boosted_hex]); + let (_, poc_rewards_map) = tokio::join!( + rewarder::reward_poc_and_dc( + &pool, + &hex_boosting_client, + &mobile_rewards_client, + &speedtest_avg_client, + &epoch, + dec!(0.0001) + ), + async move { + let one = mobile_rewards.receive_radio_reward().await; + let two = mobile_rewards.receive_radio_reward().await; + let three = mobile_rewards.receive_radio_reward().await; + let four = mobile_rewards.receive_radio_reward().await; + + mobile_rewards.assert_no_messages(); + + vec![one, two, three, four] + .into_iter() + .map(|r| (r.hotspot_key.clone(), r)) + .collect::>() + } + ); + + let boosted_cbrs = poc_rewards_map.get(boosted_cbrs_pubkey.as_ref()).unwrap(); + let unboosted_cbrs = poc_rewards_map.get(unboosted_cbrs_pubkey.as_ref()).unwrap(); + let wifi1 = poc_rewards_map.get(wifi_pubkey1.as_ref()).unwrap(); + let wifi2 = poc_rewards_map.get(wifi_pubkey2.as_ref()).unwrap(); + + assert_eq!(5, boosted_cbrs.poc_reward / unboosted_cbrs.poc_reward,); + assert_eq!(wifi1.poc_reward, wifi2.poc_reward); + + Ok(()) +} + async fn receive_expected_rewards( mobile_rewards: &mut MockFileSinkReceiver, ) -> anyhow::Result<(Vec, UnallocatedReward)> { @@ -2099,6 +2192,14 @@ impl HexBoostableRadio { Self::new(pubkey, BoostedHexDeviceType::CbrsIndoor, location) } + fn cbrs_outdoor(pubkey: PublicKeyBinary, location: Cell) -> Self { + Self::new(pubkey, BoostedHexDeviceType::CbrsOutdoor, location) + } + + fn wifi_outdoor(pubkey: PublicKeyBinary, location: Cell) -> Self { + Self::new(pubkey, BoostedHexDeviceType::WifiOutdoor, location) + } + fn new(pubkey: PublicKeyBinary, boost_type: BoostedHexDeviceType, location: Cell) -> Self { let (hb_type, is_indoor, cbsd_id) = match boost_type { BoostedHexDeviceType::All => panic!("a radio cannot be all types at once"), From 19898b717b23eec172d4f093a1ecae6efcfb5948 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 5 Sep 2024 17:17:52 -0700 Subject: [PATCH 12/15] update coverage_map to allow for tracking boosts by device type --- coverage_map/src/indoor.rs | 40 ++++++++++++----- coverage_map/src/lib.rs | 64 +++++++++++++++++++-------- coverage_map/src/outdoor.rs | 19 +++++--- mobile_config/src/boosted_hex_info.rs | 15 ++++++- 4 files changed, 99 insertions(+), 39 deletions(-) diff --git a/coverage_map/src/indoor.rs b/coverage_map/src/indoor.rs index 5cbd0ed56..2ba047070 100644 --- a/coverage_map/src/indoor.rs +++ b/coverage_map/src/indoor.rs @@ -7,7 +7,9 @@ use chrono::{DateTime, Utc}; use hex_assignments::assignment::HexAssignments; use hextree::Cell; -use crate::{BoostedHexMap, CoverageObject, RankedCoverage, SignalLevel, UnrankedCoverage}; +use crate::{ + BoostedHexMap, CoverageObject, DeviceType, RankedCoverage, SignalLevel, UnrankedCoverage, +}; pub type IndoorCellTree = HashMap>>; @@ -88,10 +90,11 @@ pub fn clone_indoor_coverage_into_submap( pub fn into_indoor_coverage_map( indoor: IndoorCellTree, boosted_hexes: &impl BoostedHexMap, + device_type: DeviceType, epoch_start: DateTime, ) -> impl Iterator + '_ { indoor.into_iter().flat_map(move |(hex, radios)| { - let boosted = boosted_hexes.get_current_multiplier(hex, epoch_start); + let boosted = boosted_hexes.get_current_multiplier(hex, device_type, epoch_start); radios .into_values() .flat_map(move |radios| radios.into_sorted_vec().into_iter()) @@ -130,10 +133,14 @@ mod test { { insert_indoor_coverage_object(&mut indoor_coverage, cov_obj); } - let ranked: HashMap<_, _> = - into_indoor_coverage_map(indoor_coverage, &NoBoostedHexes, Utc::now()) - .map(|x| (x.cbsd_id.clone().unwrap(), x)) - .collect(); + let ranked: HashMap<_, _> = into_indoor_coverage_map( + indoor_coverage, + &NoBoostedHexes, + DeviceType::CbrsIndoor, + Utc::now(), + ) + .map(|x| (x.cbsd_id.clone().unwrap(), x)) + .collect(); assert_eq!(ranked.get("3").unwrap().rank, 1); assert!({ let rank = ranked.get("2").unwrap().rank; @@ -172,10 +179,14 @@ mod test { { insert_indoor_coverage_object(&mut indoor_coverage, cov_obj); } - let ranked: HashMap<_, _> = - into_indoor_coverage_map(indoor_coverage, &NoBoostedHexes, Utc::now()) - .map(|x| (x.cbsd_id.clone().unwrap(), x)) - .collect(); + let ranked: HashMap<_, _> = into_indoor_coverage_map( + indoor_coverage, + &NoBoostedHexes, + DeviceType::CbrsIndoor, + Utc::now(), + ) + .map(|x| (x.cbsd_id.clone().unwrap(), x)) + .collect(); assert_eq!(ranked.get("1").unwrap().rank, 9); assert_eq!(ranked.get("2").unwrap().rank, 5); assert_eq!(ranked.get("3").unwrap().rank, 10); @@ -225,8 +236,13 @@ mod test { ), ); - let coverage = into_indoor_coverage_map(indoor_coverage, &NoBoostedHexes, Utc::now()) - .collect::>(); + let coverage = into_indoor_coverage_map( + indoor_coverage, + &NoBoostedHexes, + DeviceType::CbrsIndoor, + Utc::now(), + ) + .collect::>(); // Both coverages should be ranked 1 assert_eq!(coverage[0].rank, 1); assert_eq!(coverage[1].rank, 1); diff --git a/coverage_map/src/lib.rs b/coverage_map/src/lib.rs index fc4d13618..4b83a1526 100644 --- a/coverage_map/src/lib.rs +++ b/coverage_map/src/lib.rs @@ -10,6 +10,14 @@ mod outdoor; use indoor::*; use outdoor::*; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeviceType { + WifiIndoor, + WifiOutdoor, + CbrsIndoor, + CbrsOutdoor, +} + /// Data structure for keeping track of the ranking the coverage in each hex cell for indoor /// and outdoor CBRS and WiFi radios. #[derive(Clone, Default, Debug)] @@ -80,23 +88,31 @@ impl CoverageMapBuilder { ) -> CoverageMap { let mut wifi_hotspots = HashMap::<_, Vec>::new(); let mut cbrs_radios = HashMap::<_, Vec>::new(); - for coverage in into_indoor_coverage_map(self.indoor_cbrs, boosted_hexes, epoch_start) - .chain(into_indoor_coverage_map( - self.indoor_wifi, - boosted_hexes, - epoch_start, - )) - .chain(into_outdoor_coverage_map( - self.outdoor_cbrs, - boosted_hexes, - epoch_start, - )) - .chain(into_outdoor_coverage_map( - self.outdoor_wifi, - boosted_hexes, - epoch_start, - )) - { + + for coverage in into_indoor_coverage_map( + self.indoor_cbrs, + boosted_hexes, + DeviceType::CbrsIndoor, + epoch_start, + ) + .chain(into_indoor_coverage_map( + self.indoor_wifi, + boosted_hexes, + DeviceType::WifiIndoor, + epoch_start, + )) + .chain(into_outdoor_coverage_map( + self.outdoor_cbrs, + boosted_hexes, + DeviceType::CbrsOutdoor, + epoch_start, + )) + .chain(into_outdoor_coverage_map( + self.outdoor_wifi, + boosted_hexes, + DeviceType::WifiOutdoor, + epoch_start, + )) { if let Some(ref cbsd_id) = coverage.cbsd_id { cbrs_radios .entry(cbsd_id.clone()) @@ -183,7 +199,12 @@ pub enum SignalLevel { } pub trait BoostedHexMap { - fn get_current_multiplier(&self, cell: Cell, ts: DateTime) -> Option; + fn get_current_multiplier( + &self, + cell: Cell, + device_type: DeviceType, + ts: DateTime, + ) -> Option; } #[cfg(test)] @@ -191,7 +212,12 @@ pub(crate) struct NoBoostedHexes; #[cfg(test)] impl BoostedHexMap for NoBoostedHexes { - fn get_current_multiplier(&self, _cell: Cell, _ts: DateTime) -> Option { + fn get_current_multiplier( + &self, + _cell: Cell, + _device_type: DeviceType, + _ts: DateTime, + ) -> Option { None } } diff --git a/coverage_map/src/outdoor.rs b/coverage_map/src/outdoor.rs index 28c3a7e1a..b73b19644 100644 --- a/coverage_map/src/outdoor.rs +++ b/coverage_map/src/outdoor.rs @@ -7,7 +7,9 @@ use chrono::{DateTime, Utc}; use hex_assignments::assignment::HexAssignments; use hextree::Cell; -use crate::{BoostedHexMap, CoverageObject, RankedCoverage, SignalLevel, UnrankedCoverage}; +use crate::{ + BoostedHexMap, CoverageObject, DeviceType, RankedCoverage, SignalLevel, UnrankedCoverage, +}; /// Data structure for storing outdoor radios ranked by their coverage level pub type OutdoorCellTree = HashMap>; @@ -96,10 +98,11 @@ pub fn clone_outdoor_coverage_into_submap( pub fn into_outdoor_coverage_map( outdoor: OutdoorCellTree, boosted_hexes: &impl BoostedHexMap, + device_type: DeviceType, epoch_start: DateTime, ) -> impl Iterator + '_ { outdoor.into_iter().flat_map(move |(hex, radios)| { - let boosted = boosted_hexes.get_current_multiplier(hex, epoch_start); + let boosted = boosted_hexes.get_current_multiplier(hex, device_type, epoch_start); radios .into_sorted_vec() .into_iter() @@ -138,10 +141,14 @@ mod test { { insert_outdoor_coverage_object(&mut outdoor_coverage, cov_obj); } - let ranked: HashMap<_, _> = - into_outdoor_coverage_map(outdoor_coverage, &NoBoostedHexes, Utc::now()) - .map(|x| (x.cbsd_id.clone().unwrap(), x)) - .collect(); + let ranked: HashMap<_, _> = into_outdoor_coverage_map( + outdoor_coverage, + &NoBoostedHexes, + DeviceType::CbrsOutdoor, + Utc::now(), + ) + .map(|x| (x.cbsd_id.clone().unwrap(), x)) + .collect(); assert_eq!(ranked.get("5").unwrap().rank, 1); assert_eq!(ranked.get("4").unwrap().rank, 2); assert_eq!(ranked.get("3").unwrap().rank, 3); diff --git a/mobile_config/src/boosted_hex_info.rs b/mobile_config/src/boosted_hex_info.rs index 1601b0a85..77076b727 100644 --- a/mobile_config/src/boosted_hex_info.rs +++ b/mobile_config/src/boosted_hex_info.rs @@ -234,8 +234,19 @@ impl BoostedHexes { } impl coverage_map::BoostedHexMap for BoostedHexes { - fn get_current_multiplier(&self, location: Cell, ts: DateTime) -> Option { - self.get_current_multiplier(location, ts) + fn get_current_multiplier( + &self, + location: Cell, + device_type: coverage_map::DeviceType, + ts: DateTime, + ) -> Option { + let boosted_device_type = match device_type { + coverage_map::DeviceType::WifiIndoor => BoostedHexDeviceType::WifiIndoor, + coverage_map::DeviceType::WifiOutdoor => BoostedHexDeviceType::WifiOutdoor, + coverage_map::DeviceType::CbrsIndoor => BoostedHexDeviceType::CbrsIndoor, + coverage_map::DeviceType::CbrsOutdoor => BoostedHexDeviceType::CbrsOutdoor, + }; + self.get_current_multiplier(location, boosted_device_type, ts) } } From 7f448a7316f9c1fd35bb2e0e93d68e5817db611e Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Thu, 5 Sep 2024 17:18:53 -0700 Subject: [PATCH 13/15] update hex boosting tests for radio_reward_v2 It becomes more difficult to compare ratios with radio_reward_v2 because boosted rewards are scaled. In many of the test we were hoping to have nice ratios (1:10) of poc_rewards. However, this was mostly because a radio would have received 0 boosted_rewards. Checking that ratio is impossible. We can however check the coverage_points (now that they mean points, and not rewards) for the expected ratio, and rely on other tests to ensure we're calculating poc_reward amount correctly. --- .../tests/coverage_point_calculator.rs | 1 + .../tests/integrations/hex_boosting.rs | 55 ++++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/coverage_point_calculator/tests/coverage_point_calculator.rs b/coverage_point_calculator/tests/coverage_point_calculator.rs index b7027ac92..646e9f97c 100644 --- a/coverage_point_calculator/tests/coverage_point_calculator.rs +++ b/coverage_point_calculator/tests/coverage_point_calculator.rs @@ -402,6 +402,7 @@ impl BoostedHexMap for NoBoostedHexes { fn get_current_multiplier( &self, _cell: hextree::Cell, + _device_type: coverage_map::DeviceType, _ts: chrono::DateTime, ) -> Option { None diff --git a/mobile_verifier/tests/integrations/hex_boosting.rs b/mobile_verifier/tests/integrations/hex_boosting.rs index 76b62361a..a72fb18db 100644 --- a/mobile_verifier/tests/integrations/hex_boosting.rs +++ b/mobile_verifier/tests/integrations/hex_boosting.rs @@ -965,6 +965,7 @@ async fn test_distance_from_asserted_removes_boosting_but_not_location_trust( boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, BoostedHexInfo { // hotspot 3's location @@ -976,6 +977,7 @@ async fn test_distance_from_asserted_removes_boosting_but_not_location_trust( boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(), boost_config_pubkey: Pubkey::from_str(BOOST_CONFIG_PUBKEY).unwrap(), version: 0, + device_type: BoostedHexDeviceType::All, }, ]; @@ -1367,17 +1369,18 @@ async fn test_poc_boosted_hex_stack_multiplier(pool: PgPool) -> anyhow::Result<( ); let [radio_one, radio_two] = &poc_rewards; + assert_eq!( - radio_one.coverage_points, radio_two.coverage_points, - "radios perform equally unboosted" - ); - assert_eq!( - radio_one.poc_reward, radio_two.poc_reward, + radio_one.total_poc_reward(), + radio_two.total_poc_reward(), "radios earn equally unboosted" ); // Make sure there were no unallocated rewards - let total = poc_rewards.iter().map(|r| r.poc_reward).sum::(); + let total = poc_rewards + .iter() + .map(|r| r.total_poc_reward()) + .sum::(); assert_eq!( reward_shares::get_scheduled_tokens_for_poc(epoch.end - epoch.start) .to_u64() @@ -1435,32 +1438,28 @@ async fn test_poc_boosted_hex_stack_multiplier(pool: PgPool) -> anyhow::Result<( ); // sort by rewards ascending - poc_rewards.sort_by_key(|r| r.poc_reward); + poc_rewards.sort_by_key(|r| r.total_poc_reward()); let [unboosted, boosted] = &poc_rewards; - let boosted_hex = boosted.boosted_hexes.first().expect("boosted hex"); - assert_eq!(10, boosted_hex.multiplier); + let boosted_hexes = boosted.boosted_hexes(); + let boosted_hex = boosted_hexes.first().expect("boosted hex"); + assert_eq!(10, boosted_hex.boosted_multiplier); assert_eq!( 10, - boosted.coverage_points / unboosted.coverage_points, + boosted.total_coverage_points() / unboosted.total_coverage_points(), "boosted radio should have 10x coverage_points" ); - assert_eq!( - 10, - boosted.poc_reward / unboosted.poc_reward, - "boosted radio should have 10x poc_rewards" - ); - // Make sure there were no unallocated rewards - let total = poc_rewards.iter().map(|r| r.poc_reward).sum::(); - assert_eq!( - reward_shares::get_scheduled_tokens_for_poc(epoch.end - epoch.start) - .to_u64() - .unwrap(), - total, - "allocated equals scheduled output" - ); + // We expect a single unallocated + let total = poc_rewards + .iter() + .map(|r| r.total_poc_reward()) + .sum::(); + let allocated = reward_shares::get_scheduled_tokens_for_poc(epoch.end - epoch.start) + .to_u64() + .unwrap(); + assert_eq!(allocated - 1, total, "allocated equals scheduled output"); } Ok(()) @@ -1553,8 +1552,11 @@ async fn test_poc_boosted_hex_only_applies_to_device_type(pool: PgPool) -> anyho let wifi1 = poc_rewards_map.get(wifi_pubkey1.as_ref()).unwrap(); let wifi2 = poc_rewards_map.get(wifi_pubkey2.as_ref()).unwrap(); - assert_eq!(5, boosted_cbrs.poc_reward / unboosted_cbrs.poc_reward,); - assert_eq!(wifi1.poc_reward, wifi2.poc_reward); + assert_eq!( + 5, + boosted_cbrs.total_coverage_points() / unboosted_cbrs.total_coverage_points(), + ); + assert_eq!(wifi1.total_coverage_points(), wifi2.total_coverage_points()); Ok(()) } @@ -2249,6 +2251,7 @@ impl HexBoostableRadio { coverage_object: Some(cov_obj.coverage_object.uuid), location_validation_timestamp: Some(time_behind), timestamp: time_ahead, + location_source: LocationSource::Skyhook, }, cell_type: CellType::NovaGenericWifiIndoor, distance_to_asserted: Some(10), From 33f0f88a07c69b1d6537e197fc98b59aee10a4ae Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 6 Sep 2024 11:40:34 -0700 Subject: [PATCH 14/15] update to updated proto --- Cargo.lock | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a06e8f3ae..2500d18e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,17 +1617,17 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "beacon" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#376765fe006051d6dcccf709def58e7ed291b845" +source = "git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type#dcf951fe0c8d9d58aef29dc128f9176e9276dfb3" dependencies = [ "base64 0.21.7", "byteorder", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "prost", "rand 0.8.5", "rand_chacha 0.3.0", "rust_decimal", "serde", - "sha2 0.10.8", + "sha2 0.9.9", "thiserror", ] @@ -1776,7 +1776,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "http-serde", "humantime-serde", @@ -2618,7 +2618,7 @@ dependencies = [ "axum 0.7.4", "bs58 0.4.0", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "notify", "serde", @@ -3200,7 +3200,7 @@ dependencies = [ "futures-util", "h3o", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "hex-literal", "http 0.2.11", "lazy_static", @@ -3776,7 +3776,7 @@ dependencies = [ "h3o", "helium-anchor-gen", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=master)", "hex", "itertools", "jsonrpc_client", @@ -3802,6 +3802,18 @@ dependencies = [ name = "helium-proto" version = "0.1.0" source = "git+https://github.com/helium/proto?branch=master#376765fe006051d6dcccf709def58e7ed291b845" +dependencies = [ + "bytes", + "prost", + "prost-build", + "serde", + "serde_json", +] + +[[package]] +name = "helium-proto" +version = "0.1.0" +source = "git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type#dcf951fe0c8d9d58aef29dc128f9176e9276dfb3" dependencies = [ "bytes", "prost", @@ -3853,7 +3865,7 @@ dependencies = [ "async-trait", "chrono", "derive_builder", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "hextree", "rust_decimal", "rust_decimal_macros", @@ -4269,7 +4281,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "humantime-serde", "metrics", @@ -4338,7 +4350,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "hextree", "http 0.2.11", "http-serde", @@ -4380,7 +4392,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "http-serde", "humantime-serde", @@ -4422,7 +4434,7 @@ dependencies = [ "futures-util", "h3o", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http-serde", "humantime-serde", "iot-config", @@ -5010,7 +5022,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "hextree", "http 0.2.11", "http-serde", @@ -5050,7 +5062,7 @@ dependencies = [ "futures", "h3o", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "mobile-config", "prost", "rand 0.8.5", @@ -5086,7 +5098,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "http-serde", "humantime-serde", @@ -5130,7 +5142,7 @@ dependencies = [ "futures-util", "h3o", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "hex-assignments", "hextree", "http-serde", @@ -5813,7 +5825,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "http 0.2.11", "hyper 0.14.28", "jsonrpsee", @@ -5896,7 +5908,7 @@ dependencies = [ "futures-util", "helium-anchor-gen", "helium-lib", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "humantime-serde", "metrics", "metrics-exporter-prometheus", @@ -6503,7 +6515,7 @@ dependencies = [ "futures", "futures-util", "helium-crypto", - "helium-proto", + "helium-proto 0.1.0 (git+https://github.com/helium/proto?branch=mj/hip-109-boost-by-device-type)", "humantime-serde", "lazy_static", "metrics", @@ -9851,7 +9863,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.9.9", "thiserror", "twox-hash", "xorf", From a9229e42d2630295099d29873570ccd84b157169 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Fri, 6 Sep 2024 16:33:06 -0700 Subject: [PATCH 15/15] remove unnecessary comment --- boost_manager/src/activator.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/boost_manager/src/activator.rs b/boost_manager/src/activator.rs index d51586e80..9d7759fee 100644 --- a/boost_manager/src/activator.rs +++ b/boost_manager/src/activator.rs @@ -95,7 +95,6 @@ where txn: &mut Transaction<'_, Postgres>, manifest: RewardManifest, ) -> Result<()> { - // get latest boosted hexes info from mobile config let boosted_hexes = BoostedHexes::get_active(&self.hex_boosting_client).await?; // get the rewards file from the manifest