Skip to content

Commit

Permalink
determine oracle multiplier based on boost value >=1 (#764)
Browse files Browse the repository at this point in the history
* determine oracle multiplier based on whether hex is boosted and not solely on value of boost

* multiplier as NonZeroU32

* add boost tests, assert invalid multiplier value
  • Loading branch information
andymck authored Mar 21, 2024
1 parent 97d8188 commit 0f5718f
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 62 deletions.
22 changes: 18 additions & 4 deletions boost_manager/tests/activator_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use helium_proto::services::poc_mobile::BoostedHex as BoostedHexProto;
use mobile_config::boosted_hex_info::{BoostedHexInfo, BoostedHexes};
use solana_sdk::pubkey::Pubkey;
use sqlx::PgPool;
use std::{collections::HashMap, str::FromStr};
use std::{collections::HashMap, num::NonZeroU32, str::FromStr};

const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU";
const BOOST_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG";
Expand All @@ -20,13 +20,27 @@ impl TestContext {
let boost_period_length = Duration::days(30);

// setup boosted hex data to stream as updates
let multipliers1 = vec![2, 10, 15, 35];
let multipliers1 = vec![
NonZeroU32::new(2).unwrap(),
NonZeroU32::new(10).unwrap(),
NonZeroU32::new(15).unwrap(),
NonZeroU32::new(35).unwrap(),
];
let start_ts_1 = epoch.start - boost_period_length;
let end_ts_1 = start_ts_1 + (boost_period_length * multipliers1.len() as i32);
let multipliers2 = vec![3, 10, 20];
let multipliers2 = vec![
NonZeroU32::new(3).unwrap(),
NonZeroU32::new(10).unwrap(),
NonZeroU32::new(20).unwrap(),
];

let start_ts_2 = epoch.start - (boost_period_length * 2);
let end_ts_2 = start_ts_2 + (boost_period_length * multipliers2.len() as i32);
let multipliers3 = vec![1, 10, 20];
let multipliers3 = vec![
NonZeroU32::new(1).unwrap(),
NonZeroU32::new(10).unwrap(),
NonZeroU32::new(20).unwrap(),
];

let boosts = vec![
BoostedHexInfo {
Expand Down
15 changes: 12 additions & 3 deletions boost_manager/tests/watcher_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use mobile_config::{
};
use solana_sdk::pubkey::Pubkey;
use sqlx::PgPool;
use std::str::FromStr;
use std::{num::NonZeroU32, str::FromStr};

const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU";
const BOOST_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG";
Expand Down Expand Up @@ -47,10 +47,19 @@ async fn test_boosted_hex_updates_to_filestore(pool: PgPool) -> anyhow::Result<(
let boost_period_length = Duration::days(30);

// setup boosted hex data to stream as updates
let multipliers1 = vec![2, 10, 15, 35];
let multipliers1 = vec![
NonZeroU32::new(2).unwrap(),
NonZeroU32::new(10).unwrap(),
NonZeroU32::new(15).unwrap(),
NonZeroU32::new(35).unwrap(),
];
let start_ts_1 = epoch.start - boost_period_length;
let end_ts_1 = start_ts_1 + (boost_period_length * multipliers1.len() as i32);
let multipliers2 = vec![3, 10, 20];
let multipliers2 = vec![
NonZeroU32::new(3).unwrap(),
NonZeroU32::new(10).unwrap(),
NonZeroU32::new(20).unwrap(),
];
let start_ts_2 = epoch.start - (boost_period_length * 2);
let end_ts_2 = start_ts_2 + (boost_period_length * multipliers2.len() as i32);

Expand Down
150 changes: 140 additions & 10 deletions mobile_config/src/boosted_hex_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use file_store::traits::TimestampDecode;
use futures::stream::{BoxStream, StreamExt};
use helium_proto::BoostedHexInfoV1 as BoostedHexInfoProto;
use solana_sdk::pubkey::Pubkey;
use std::{collections::HashMap, convert::TryFrom};
use std::{collections::HashMap, convert::TryFrom, num::NonZeroU32};

pub type BoostedHexInfoStream = BoxStream<'static, BoostedHexInfo>;

Expand All @@ -18,7 +18,7 @@ pub struct BoostedHexInfo {
pub start_ts: Option<DateTime<Utc>>,
pub end_ts: Option<DateTime<Utc>>,
pub period_length: Duration,
pub multipliers: Vec<u32>,
pub multipliers: Vec<NonZeroU32>,
pub boosted_hex_pubkey: Pubkey,
pub boost_config_pubkey: Pubkey,
pub version: u32,
Expand All @@ -28,7 +28,12 @@ impl TryFrom<BoostedHexInfoProto> for BoostedHexInfo {
type Error = anyhow::Error;
fn try_from(v: BoostedHexInfoProto) -> anyhow::Result<Self> {
let period_length = Duration::seconds(v.period_length as i64);
let multipliers = v.multipliers;
let multipliers = v
.multipliers
.into_iter()
.map(NonZeroU32::new)
.collect::<Option<Vec<_>>>()
.ok_or_else(|| anyhow::anyhow!("multipliers cannot contain values of 0"))?;
let start_ts = to_start_ts(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())?;
Expand All @@ -52,12 +57,17 @@ impl TryFrom<BoostedHexInfo> for BoostedHexInfoProto {
fn try_from(v: BoostedHexInfo) -> anyhow::Result<Self> {
let start_ts = v.start_ts.map_or(0, |v| v.timestamp() as u64);
let end_ts = v.end_ts.map_or(0, |v| v.timestamp() as u64);
let multipliers = v
.multipliers
.into_iter()
.map(|v| v.get())
.collect::<Vec<_>>();
Ok(Self {
location: v.location,
start_ts,
end_ts,
period_length: v.period_length.num_seconds() as u32,
multipliers: v.multipliers,
multipliers,
boosted_hex_pubkey: v.boosted_hex_pubkey.to_bytes().into(),
boost_config_pubkey: v.boost_config_pubkey.to_bytes().into(),
version: v.version,
Expand All @@ -66,7 +76,7 @@ impl TryFrom<BoostedHexInfo> for BoostedHexInfoProto {
}

impl BoostedHexInfo {
pub fn current_multiplier(&self, ts: DateTime<Utc>) -> anyhow::Result<Option<u32>> {
pub fn current_multiplier(&self, ts: DateTime<Utc>) -> anyhow::Result<Option<NonZeroU32>> {
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
Expand Down Expand Up @@ -98,7 +108,7 @@ pub struct BoostedHexes {
#[derive(PartialEq, Debug, Clone)]
pub struct BoostedHex {
pub location: u64,
pub multiplier: u32,
pub multiplier: NonZeroU32,
}

impl BoostedHexes {
Expand All @@ -113,7 +123,6 @@ impl BoostedHexes {
pub async fn get_all(
hex_service_client: &impl HexBoostingInfoResolver<Error = ClientError>,
) -> anyhow::Result<Self> {
tracing::info!("getting boosted hexes");
let mut map = HashMap::new();
let mut stream = hex_service_client
.clone()
Expand Down Expand Up @@ -144,7 +153,7 @@ impl BoostedHexes {
Ok(Self { hexes: map })
}

pub fn get_current_multiplier(&self, location: u64, ts: DateTime<Utc>) -> Option<u32> {
pub fn get_current_multiplier(&self, location: u64, ts: DateTime<Utc>) -> Option<NonZeroU32> {
self.hexes
.get(&location)
.and_then(|info| info.current_multiplier(ts).ok()?)
Expand All @@ -157,6 +166,7 @@ pub(crate) mod db {
use futures::stream::{Stream, StreamExt};
use solana_sdk::pubkey::Pubkey;
use sqlx::{PgExecutor, Row};
use std::num::NonZeroU32;
use std::str::FromStr;

const GET_BOOSTED_HEX_INFO_SQL: &str = r#"
Expand Down Expand Up @@ -214,8 +224,11 @@ pub(crate) mod db {
let multipliers = row
.get::<Vec<u8>, &str>("multipliers")
.into_iter()
.map(|v| v as u32)
.collect::<Vec<_>>();
.map(|v| NonZeroU32::new(v as u32))
.collect::<Option<Vec<_>>>()
.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 boost_config_pubkey =
Pubkey::from_str(row.get::<&str, &str>("boost_config_pubkey"))
Expand Down Expand Up @@ -252,3 +265,120 @@ fn to_end_ts(
) -> Option<DateTime<Utc>> {
start_ts.map(|ts| ts + period_length * num_multipliers as i32)
}

#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDateTime;
use std::str::FromStr;

const BOOST_HEX_PUBKEY: &str = "J9JiLTpjaShxL8eMvUs8txVw6TZ36E38SiJ89NxnMbLU";
const BOOST_HEX_CONFIG_PUBKEY: &str = "BZM1QTud72B2cpTW7PhEnFmRX7ZWzvY7DpPpNJJuDrWG";

#[test]
fn boosted_hex_from_proto_valid_not_started() -> anyhow::Result<()> {
let proto = BoostedHexInfoProto {
location: 631252734740306943,
start_ts: 0,
end_ts: 0,
period_length: 2592000,
multipliers: vec![2, 10, 15, 35],
boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)
.unwrap()
.to_bytes()
.to_vec(),
boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)
.unwrap()
.to_bytes()
.to_vec(),
version: 1,
};

let msg = BoostedHexInfo::try_from(proto)?;
assert_eq!(631252734740306943, msg.location);
assert_eq!(None, msg.start_ts);
assert_eq!(None, msg.end_ts);
assert_eq!(2592000, msg.period_length.num_seconds());
assert_eq!(4, msg.multipliers.len());
assert_eq!(2, msg.multipliers[0].get());
assert_eq!(10, msg.multipliers[1].get());
assert_eq!(15, msg.multipliers[2].get());
assert_eq!(35, msg.multipliers[3].get());
assert_eq!(
Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(),
msg.boosted_hex_pubkey
);
assert_eq!(
Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY).unwrap(),
msg.boost_config_pubkey
);
assert_eq!(1, msg.version);
Ok(())
}

#[test]
fn boosted_hex_from_proto_valid_started() -> anyhow::Result<()> {
let proto = BoostedHexInfoProto {
location: 631252734740306943,
start_ts: 1710378000,
end_ts: 1720746000,
period_length: 2592000,
multipliers: vec![2, 10, 15, 35],
boosted_hex_pubkey: Pubkey::from_str(BOOST_HEX_PUBKEY)
.unwrap()
.to_bytes()
.to_vec(),
boost_config_pubkey: Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY)
.unwrap()
.to_bytes()
.to_vec(),
version: 1,
};

let msg = BoostedHexInfo::try_from(proto)?;
assert_eq!(631252734740306943, msg.location);
assert_eq!(parse_dt("2024-03-14 01:00:00"), msg.start_ts.unwrap());
assert_eq!(parse_dt("2024-07-12 01:00:00"), msg.end_ts.unwrap());
assert_eq!(2592000, msg.period_length.num_seconds());
assert_eq!(4, msg.multipliers.len());
assert_eq!(2, msg.multipliers[0].get());
assert_eq!(10, msg.multipliers[1].get());
assert_eq!(15, msg.multipliers[2].get());
assert_eq!(35, msg.multipliers[3].get());
assert_eq!(
Pubkey::from_str(BOOST_HEX_PUBKEY).unwrap(),
msg.boosted_hex_pubkey
);
assert_eq!(
Pubkey::from_str(BOOST_HEX_CONFIG_PUBKEY).unwrap(),
msg.boost_config_pubkey
);
assert_eq!(1, msg.version);
Ok(())
}

#[test]
fn boosted_hex_from_proto_invalid_multiplier() -> anyhow::Result<()> {
let proto = BoostedHexInfoProto {
location: 631252734740306943,
start_ts: 1712624400000,
end_ts: 0,
period_length: 2592000,
multipliers: vec![2, 0, 15, 35],
boosted_hex_pubkey: BOOST_HEX_PUBKEY.as_bytes().to_vec(),
boost_config_pubkey: BOOST_HEX_CONFIG_PUBKEY.as_bytes().to_vec(),
version: 1,
};
assert_eq!(
"multipliers cannot contain values of 0",
BoostedHexInfo::try_from(proto).err().unwrap().to_string()
);
Ok(())
}

fn parse_dt(dt: &str) -> DateTime<Utc> {
NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S")
.expect("unable_to_parse")
.and_utc()
}
}
Loading

0 comments on commit 0f5718f

Please sign in to comment.