From c9e98cb67c95312ab8b2f628c0a5bec4fffcfbf2 Mon Sep 17 00:00:00 2001 From: Michael Jeffrey Date: Wed, 8 May 2024 10:21:45 -0700 Subject: [PATCH] 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 0e31bb548..6bf08421d 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,13 +228,14 @@ 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); } } 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; @@ -239,39 +246,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() @@ -291,7 +330,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() @@ -300,7 +339,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)))?; @@ -334,7 +373,7 @@ pub(crate) mod db { } } -fn to_start_ts(timestamp: u64) -> Option> { +fn to_timestamp(timestamp: u64) -> Option> { if timestamp == 0 { None } else { @@ -389,23 +428,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), @@ -525,150 +552,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 77a56ed23..167e674ce 100644 --- a/mobile_config/src/hex_boosting_service.rs +++ b/mobile_config/src/hex_boosting_service.rs @@ -70,7 +70,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 70989003a..7eaa0a6e4 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -391,7 +391,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 verified_radio_thresholds = 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 5f62eadf9..a4cec3979 100644 --- a/mobile_verifier/tests/integrations/modeled_coverage.rs +++ b/mobile_verifier/tests/integrations/modeled_coverage.rs @@ -844,41 +844,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);