diff --git a/mobile_verifier/src/reward_shares.rs b/mobile_verifier/src/reward_shares.rs index a052abfb6..ae1fef359 100644 --- a/mobile_verifier/src/reward_shares.rs +++ b/mobile_verifier/src/reward_shares.rs @@ -552,6 +552,13 @@ pub fn get_scheduled_tokens_for_service_providers(total_emission_pool: Decimal) total_emission_pool * SERVICE_PROVIDER_PERCENT } +pub fn get_scheduled_tokens_total(total_emission_pool: Decimal) -> u64 { + (total_emission_pool + * (SERVICE_PROVIDER_PERCENT + POC_REWARDS_PERCENT + MAX_DATA_TRANSFER_REWARDS_PERCENT)) + .to_u64() + .unwrap_or(0) +} + #[derive(Display, EnumString)] pub enum RewardableEntityKey { #[strum(serialize = "Helium Mobile")] diff --git a/mobile_verifier/src/rewarder.rs b/mobile_verifier/src/rewarder.rs index 83fa45ab5..2ec566079 100644 --- a/mobile_verifier/src/rewarder.rs +++ b/mobile_verifier/src/rewarder.rs @@ -5,8 +5,8 @@ use crate::{ heartbeats::{self, HeartbeatReward}, resolve_subdao_pubkey, reward_shares::{ - get_scheduled_tokens_for_service_providers, CalculatedPocRewardShares, CoverageShares, - DataTransferAndPocAllocatedRewardBuckets, TransferRewards, + CalculatedPocRewardShares, CoverageShares, DataTransferAndPocAllocatedRewardBuckets, + TransferRewards, }, speedtests, speedtests_average::SpeedtestAverages, @@ -19,12 +19,13 @@ use file_store::{file_sink::FileSinkClient, file_upload::FileUpload, traits::Tim use file_store_oracles::traits::{FileSinkCommitStrategy, FileSinkRollTime, FileSinkWriteExt}; use self::boosted_hex_eligibility::BoostedHexEligibility; -use crate::reward_shares::{RewardableEntityKey, HELIUM_MOBILE_SERVICE_REWARD_BONES}; +use crate::reward_shares::{ + get_scheduled_tokens_total, RewardableEntityKey, HELIUM_MOBILE_SERVICE_REWARD_BONES, +}; use helium_proto::{ reward_manifest::RewardData::MobileRewardData, services::poc_mobile::{ self as proto, mobile_reward_share::Reward as ProtoReward, MobileRewardShare, - UnallocatedReward, UnallocatedRewardType, }, MobileRewardData as ManifestMobileRewardData, MobileRewardToken, RewardManifest, ServiceProvider, @@ -271,8 +272,7 @@ where reward_info.epoch_emissions, ); - // process rewards for poc and data transfer - let poc_dc_shares = reward_poc_and_dc( + let poc_dc_shares = distribute_rewards( &self.pool, &self.hex_service_client, self.mobile_rewards.clone(), @@ -281,9 +281,6 @@ where ) .await?; - // process rewards for service providers - reward_service_providers(self.mobile_rewards.clone(), &reward_info).await?; - self.speedtest_averages.commit().await?; let written_files = self.mobile_rewards.commit().await?.await??; @@ -349,13 +346,38 @@ where } } -pub async fn reward_poc_and_dc( +pub async fn distribute_rewards( pool: &Pool, hex_service_client: &impl HexBoostingInfoResolver, mobile_rewards: FileSinkClient, reward_info: &EpochRewardInfo, price_info: PriceInfo, ) -> anyhow::Result { + // process rewards for poc and data transfer + let (poc_dc_shares, poc_dc_allocated_amount, _) = reward_poc_and_dc( + pool, + hex_service_client, + mobile_rewards.clone(), + reward_info, + price_info.clone(), + ) + .await?; + + // process rewards for service providers + let total_rewards = get_scheduled_tokens_total(reward_info.epoch_emissions); + let sp_reward_amount = total_rewards - poc_dc_allocated_amount.to_u64().unwrap_or(0); + reward_service_providers(mobile_rewards.clone(), reward_info, sp_reward_amount).await?; + + Ok(poc_dc_shares) +} + +pub async fn reward_poc_and_dc( + pool: &Pool, + hex_service_client: &impl HexBoostingInfoResolver, + mobile_rewards: FileSinkClient, + reward_info: &EpochRewardInfo, + price_info: PriceInfo, +) -> anyhow::Result<(CalculatedPocRewardShares, Decimal, Decimal)> { let mut reward_shares = DataTransferAndPocAllocatedRewardBuckets::new(reward_info.epoch_emissions); @@ -374,9 +396,7 @@ pub async fn reward_poc_and_dc( }; telemetry::data_transfer_rewards_scale(scale); - // reward dc before poc so that we can calculate the unallocated dc reward - // and carry this into the poc pool - let dc_unallocated_amount = reward_dc( + let (dc_allocated_amount, dc_unallocated_amount) = reward_dc( &mobile_rewards, reward_info, transfer_rewards, @@ -385,7 +405,7 @@ pub async fn reward_poc_and_dc( .await?; reward_shares.handle_unallocated_data_transfer(dc_unallocated_amount); - let (poc_unallocated_amount, calculated_poc_reward_shares) = reward_poc( + let (calculated_poc_reward_shares, poc_allocated_amount, poc_unallocated_amount) = reward_poc( pool, hex_service_client, &mobile_rewards, @@ -394,20 +414,11 @@ pub async fn reward_poc_and_dc( ) .await?; - let poc_unallocated_amount = poc_unallocated_amount - .round_dp_with_strategy(0, RoundingStrategy::ToZero) - .to_u64() - .unwrap_or(0); - - write_unallocated_reward( - &mobile_rewards, - UnallocatedRewardType::Poc, + Ok(( + calculated_poc_reward_shares, + poc_allocated_amount + dc_allocated_amount, poc_unallocated_amount, - reward_info, - ) - .await?; - - Ok(calculated_poc_reward_shares) + )) } pub async fn reward_poc( @@ -416,7 +427,7 @@ pub async fn reward_poc( mobile_rewards: &FileSinkClient, reward_info: &EpochRewardInfo, reward_shares: DataTransferAndPocAllocatedRewardBuckets, -) -> anyhow::Result<(Decimal, CalculatedPocRewardShares)> { +) -> anyhow::Result<(CalculatedPocRewardShares, Decimal, Decimal)> { let heartbeats = HeartbeatReward::validated(pool, &reward_info.epoch_period); let speedtest_averages = SpeedtestAverages::aggregate_epoch_averages(reward_info.epoch_period.end, pool).await?; @@ -443,34 +454,38 @@ pub async fn reward_poc( let total_poc_rewards = reward_shares.total_poc(); - let (unallocated_poc_amount, calculated_poc_rewards_per_share) = - if let Some((calculated_poc_rewards_per_share, mobile_reward_shares)) = - coverage_shares.into_rewards(reward_shares, &reward_info.epoch_period) - { - // handle poc reward outputs - let mut allocated_poc_rewards = 0_u64; - let mut count_rewarded_radios = 0; - for (poc_reward_amount, mobile_reward_share_v2) in mobile_reward_shares { - allocated_poc_rewards += poc_reward_amount; - count_rewarded_radios += 1; - mobile_rewards - .write(mobile_reward_share_v2, []) - .await? - // await the returned one shot to ensure that we wrote the file - .await??; - } - telemetry::poc_rewarded_radios(count_rewarded_radios); - // calculate any unallocated poc reward - ( - total_poc_rewards - Decimal::from(allocated_poc_rewards), - calculated_poc_rewards_per_share, - ) - } else { - telemetry::poc_rewarded_radios(0); - // default unallocated poc reward to the total poc reward - (total_poc_rewards, CalculatedPocRewardShares::default()) - }; - Ok((unallocated_poc_amount, calculated_poc_rewards_per_share)) + if let Some((calculated_poc_rewards_per_share, mobile_reward_shares)) = + coverage_shares.into_rewards(reward_shares, &reward_info.epoch_period) + { + // handle poc reward outputs + let mut allocated_poc_rewards = 0_u64; + let mut count_rewarded_radios = 0; + for (poc_reward_amount, mobile_reward_share_v2) in mobile_reward_shares { + allocated_poc_rewards += poc_reward_amount; + count_rewarded_radios += 1; + mobile_rewards + .write(mobile_reward_share_v2, []) + .await? + // await the returned one shot to ensure that we wrote the file + .await??; + } + telemetry::poc_rewarded_radios(count_rewarded_radios); + // calculate any unallocated poc reward + Ok(( + calculated_poc_rewards_per_share, + Decimal::from(allocated_poc_rewards), + total_poc_rewards - Decimal::from(allocated_poc_rewards), + )) + } else { + telemetry::poc_rewarded_radios(0); + // default unallocated poc reward to the total poc reward + let total_poc_rewards = total_poc_rewards.to_u64().unwrap_or(0); + Ok(( + CalculatedPocRewardShares::default(), + Decimal::from(0_u64), + Decimal::from(total_poc_rewards), + )) + } } pub async fn reward_dc( @@ -478,7 +493,7 @@ pub async fn reward_dc( reward_info: &EpochRewardInfo, transfer_rewards: TransferRewards, reward_shares: &DataTransferAndPocAllocatedRewardBuckets, -) -> anyhow::Result { +) -> anyhow::Result<(Decimal, Decimal)> { // handle dc reward outputs let mut allocated_dc_rewards = 0_u64; let mut count_rewarded_gateways = 0; @@ -497,21 +512,20 @@ pub async fn reward_dc( // we return the full decimal value just to ensure we allocate all to poc let unallocated_dc_reward_amount = reward_shares.data_transfer - Decimal::from(allocated_dc_rewards); - Ok(unallocated_dc_reward_amount) + Ok(( + Decimal::from(allocated_dc_rewards), + unallocated_dc_reward_amount, + )) } pub async fn reward_service_providers( mobile_rewards: FileSinkClient, reward_info: &EpochRewardInfo, + total_sp_reward_amount: u64, ) -> anyhow::Result<()> { - let total_sp_rewards = get_scheduled_tokens_for_service_providers(reward_info.epoch_emissions); - let sp_reward_amount = total_sp_rewards - .round_dp_with_strategy(0, RoundingStrategy::ToZero) - .to_u64() - .unwrap_or(0); - - let subscriber_reward = std::cmp::min(sp_reward_amount, HELIUM_MOBILE_SERVICE_REWARD_BONES); - let network_reward = sp_reward_amount.saturating_sub(subscriber_reward); + let subscriber_reward = + std::cmp::min(total_sp_reward_amount, HELIUM_MOBILE_SERVICE_REWARD_BONES); + let network_reward = total_sp_reward_amount.saturating_sub(subscriber_reward); // Write a ServiceProviderReward for HeliumMobile Subscriber Wallet for 450 HNT write_service_provider_reward( @@ -536,29 +550,6 @@ pub async fn reward_service_providers( Ok(()) } -async fn write_unallocated_reward( - mobile_rewards: &FileSinkClient, - unallocated_type: UnallocatedRewardType, - unallocated_amount: u64, - reward_info: &'_ EpochRewardInfo, -) -> anyhow::Result<()> { - if unallocated_amount > 0 { - let unallocated_reward = proto::MobileRewardShare { - start_period: reward_info.epoch_period.start.encode_timestamp(), - end_period: reward_info.epoch_period.end.encode_timestamp(), - reward: Some(ProtoReward::UnallocatedReward(UnallocatedReward { - reward_type: unallocated_type as i32, - amount: unallocated_amount, - })), - }; - mobile_rewards - .write(unallocated_reward, []) - .await? - .await??; - }; - Ok(()) -} - pub async fn next_reward_epoch(db: &Pool) -> db_store::Result { meta::fetch(db, "next_reward_epoch").await } diff --git a/mobile_verifier/tests/integrations/common/mod.rs b/mobile_verifier/tests/integrations/common/mod.rs index 8da3c5011..833de0bfb 100644 --- a/mobile_verifier/tests/integrations/common/mod.rs +++ b/mobile_verifier/tests/integrations/common/mod.rs @@ -1,3 +1,5 @@ +pub mod seed; + use chrono::{DateTime, Duration, Utc}; use file_store::{ file_sink::{FileSinkClient, Message as SinkMessage}, diff --git a/mobile_verifier/tests/integrations/common/seed.rs b/mobile_verifier/tests/integrations/common/seed.rs new file mode 100644 index 000000000..383a2c713 --- /dev/null +++ b/mobile_verifier/tests/integrations/common/seed.rs @@ -0,0 +1,303 @@ +use crate::common; +use crate::rewarder_poc_dc::proto; +use chrono::{DateTime, Duration as ChronoDuration, Utc}; +use file_store_oracles::coverage::{ + CoverageObject as FSCoverageObject, KeyType, RadioHexSignalLevel, +}; +use file_store_oracles::speedtest::CellSpeedtest; +use file_store_oracles::unique_connections::{UniqueConnectionReq, UniqueConnectionsIngestReport}; +use helium_crypto::PublicKeyBinary; +use mobile_verifier::cell_type::CellType; +use mobile_verifier::coverage::CoverageObject; +use mobile_verifier::heartbeats::{HbType, Heartbeat, ValidatedHeartbeat}; +use mobile_verifier::{data_session, speedtests, unique_connections}; +use rust_decimal_macros::dec; +use sqlx::{PgPool, Postgres, Transaction}; +use std::ops::Range; +use uuid::Uuid; + +const HOTSPOT_1: &str = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6"; +const HOTSPOT_2: &str = "11uJHS2YaEWJqgqC7yza9uvSmpv5FWoMQXiP8WbxBGgNUmifUJf"; +pub const HOTSPOT_3: &str = "112E7TxoNHV46M6tiPA8N1MkeMeQxc9ztb4JQLXBVAAUfq1kJLoF"; +const PAYER_1: &str = "11eX55faMbqZB7jzN4p67m6w7ScPMH6ubnvCjCPLh72J49PaJEL"; + +pub async fn seed_heartbeats( + ts: DateTime, + txn: &mut Transaction<'_, Postgres>, +) -> anyhow::Result<()> { + for n in 0..24 { + let hotspot_key1: PublicKeyBinary = HOTSPOT_1.to_string().parse()?; + let cov_obj_1 = create_coverage_object( + ts + ChronoDuration::hours(n), + hotspot_key1.clone(), + 0x8a1fb466d2dffff_u64, + true, + ); + let wifi_heartbeat_1 = ValidatedHeartbeat { + heartbeat: Heartbeat { + hb_type: HbType::Wifi, + hotspot_key: hotspot_key1, + operation_mode: true, + lat: 0.0, + lon: 0.0, + coverage_object: Some(cov_obj_1.coverage_object.uuid), + location_validation_timestamp: None, + timestamp: ts + ChronoDuration::hours(n), + location_source: proto::LocationSource::Gps, + }, + cell_type: CellType::NovaGenericWifiIndoor, + distance_to_asserted: Some(0), + coverage_meta: None, + location_trust_score_multiplier: dec!(1.0), + validity: proto::HeartbeatValidity::Valid, + }; + + let hotspot_key2: PublicKeyBinary = HOTSPOT_2.to_string().parse()?; + let cov_obj_2 = create_coverage_object( + ts + ChronoDuration::hours(n), + hotspot_key2.clone(), + 0x8a1fb49642dffff_u64, + true, + ); + let wifi_heartbeat_2 = ValidatedHeartbeat { + heartbeat: Heartbeat { + hb_type: HbType::Wifi, + hotspot_key: hotspot_key2, + operation_mode: true, + lat: 0.0, + lon: 0.0, + coverage_object: Some(cov_obj_2.coverage_object.uuid), + location_validation_timestamp: None, + timestamp: ts + ChronoDuration::hours(n), + location_source: proto::LocationSource::Gps, + }, + cell_type: CellType::NovaGenericWifiIndoor, + distance_to_asserted: Some(0), + coverage_meta: None, + location_trust_score_multiplier: dec!(1.0), + validity: proto::HeartbeatValidity::Valid, + }; + + let hotspot_key3: PublicKeyBinary = HOTSPOT_3.to_string().parse()?; + let cov_obj_3 = create_coverage_object( + ts + ChronoDuration::hours(n), + hotspot_key3.clone(), + 0x8c2681a306607ff_u64, + true, + ); + let wifi_heartbeat_3 = ValidatedHeartbeat { + heartbeat: Heartbeat { + hb_type: HbType::Wifi, + hotspot_key: hotspot_key3, + operation_mode: true, + lat: 0.0, + lon: 0.0, + coverage_object: Some(cov_obj_3.coverage_object.uuid), + location_validation_timestamp: Some(ts - ChronoDuration::hours(24)), + timestamp: ts + ChronoDuration::hours(n), + location_source: proto::LocationSource::Skyhook, + }, + cell_type: CellType::NovaGenericWifiIndoor, + distance_to_asserted: Some(0), + coverage_meta: None, + location_trust_score_multiplier: dec!(1.0), + validity: proto::HeartbeatValidity::Valid, + }; + + save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_3, txn).await?; + save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_1, txn).await?; + save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_2, txn).await?; + + wifi_heartbeat_1.save(txn).await?; + wifi_heartbeat_2.save(txn).await?; + wifi_heartbeat_3.save(txn).await?; + + cov_obj_1.save(txn).await?; + cov_obj_2.save(txn).await?; + cov_obj_3.save(txn).await?; + } + Ok(()) +} + +pub async fn update_assignments(pool: &PgPool) -> anyhow::Result<()> { + let _ = common::set_unassigned_oracle_boosting_assignments( + pool, + &common::mock_hex_boost_data_default(), + ) + .await?; + Ok(()) +} + +pub async fn update_assignments_bad(pool: &PgPool) -> anyhow::Result<()> { + let _ = common::set_unassigned_oracle_boosting_assignments( + pool, + &common::mock_hex_boost_data_bad(), + ) + .await?; + Ok(()) +} + +pub async fn seed_speedtests( + ts: DateTime, + txn: &mut Transaction<'_, Postgres>, +) -> anyhow::Result<()> { + for n in 0..24 { + let hotspot1_speedtest = CellSpeedtest { + pubkey: HOTSPOT_1.parse()?, + serial: "serial1".to_string(), + timestamp: ts - ChronoDuration::hours(n * 4), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 50, + }; + + let hotspot2_speedtest = CellSpeedtest { + pubkey: HOTSPOT_2.parse()?, + serial: "serial2".to_string(), + timestamp: ts - ChronoDuration::hours(n * 4), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 50, + }; + + let hotspot3_speedtest = CellSpeedtest { + pubkey: HOTSPOT_3.parse()?, + serial: "serial3".to_string(), + timestamp: ts - ChronoDuration::hours(n * 4), + upload_speed: 100_000_000, + download_speed: 100_000_000, + latency: 50, + }; + + speedtests::save_speedtest(&hotspot1_speedtest, txn).await?; + speedtests::save_speedtest(&hotspot2_speedtest, txn).await?; + speedtests::save_speedtest(&hotspot3_speedtest, txn).await?; + } + Ok(()) +} + +pub async fn seed_data_sessions( + ts: DateTime, + txn: &mut Transaction<'_, Postgres>, +) -> anyhow::Result { + let rewardable_bytes_1 = 1_024 * 1_000; + let data_session_1 = data_session::HotspotDataSession { + pub_key: HOTSPOT_1.parse()?, + payer: PAYER_1.parse()?, + upload_bytes: 1_024 * 1_000, + download_bytes: 1_024 * 50_000, + // Here to test that rewardable_bytes is the one taken into account we lower it + rewardable_bytes: rewardable_bytes_1, + num_dcs: 5_000_000, + received_timestamp: ts + ChronoDuration::hours(1), + burn_timestamp: ts + ChronoDuration::hours(1), + }; + + let rewardable_bytes_2 = 1_024 * 1_000 + 1_024 * 50_000; + let data_session_2 = data_session::HotspotDataSession { + pub_key: HOTSPOT_2.parse()?, + payer: PAYER_1.parse()?, + upload_bytes: 1_024 * 1_000, + download_bytes: 1_024 * 50_000, + rewardable_bytes: rewardable_bytes_2, + num_dcs: 5_000_000, + received_timestamp: ts + ChronoDuration::hours(1), + burn_timestamp: ts + ChronoDuration::hours(1), + }; + + let rewardable_bytes_3 = 1_024 * 1_000 + 1_024 * 50_000; + let data_session_3 = data_session::HotspotDataSession { + pub_key: HOTSPOT_3.parse()?, + payer: PAYER_1.parse()?, + upload_bytes: 1_024 * 1_000, + download_bytes: 1_024 * 50_000, + rewardable_bytes: rewardable_bytes_3, + num_dcs: 5_000_000, + received_timestamp: ts + ChronoDuration::hours(1), + burn_timestamp: ts + ChronoDuration::hours(1), + }; + data_session_1.save(txn).await?; + data_session_2.save(txn).await?; + data_session_3.save(txn).await?; + + let rewardable = rewardable_bytes_1 + rewardable_bytes_2 + rewardable_bytes_3; + + Ok(rewardable as u64) +} + +pub async fn seed_unique_connections( + txn: &mut Transaction<'_, Postgres>, + things: &[(PublicKeyBinary, u64)], + epoch: &Range>, +) -> anyhow::Result<()> { + let mut reports = vec![]; + for (pubkey, unique_connections) in things { + reports.push(UniqueConnectionsIngestReport { + received_timestamp: epoch.start + chrono::Duration::hours(1), + report: UniqueConnectionReq { + pubkey: pubkey.clone(), + start_timestamp: Utc::now(), + end_timestamp: Utc::now(), + unique_connections: *unique_connections, + timestamp: Utc::now(), + carrier_key: pubkey.clone(), + signature: vec![], + }, + }); + } + unique_connections::db::save(txn, &reports).await?; + Ok(()) +} + +fn create_coverage_object( + ts: DateTime, + pub_key: PublicKeyBinary, + hex: u64, + indoor: bool, +) -> CoverageObject { + let location = h3o::CellIndex::try_from(hex).unwrap(); + let key_type = KeyType::HotspotKey(pub_key.clone()); + let report = FSCoverageObject { + pub_key, + uuid: Uuid::new_v4(), + key_type, + coverage_claim_time: ts, + coverage: vec![RadioHexSignalLevel { + location, + signal_level: proto::SignalLevel::High, + signal_power: 1000, + }], + indoor, + trust_score: 1000, + signature: Vec::new(), + }; + CoverageObject { + coverage_object: report, + validity: proto::CoverageObjectValidity::Valid, + } +} + +async fn save_seniority_object( + ts: DateTime, + hb: &ValidatedHeartbeat, + exec: &mut Transaction<'_, Postgres>, +) -> anyhow::Result<()> { + sqlx::query( + r#" + INSERT INTO seniority + (radio_key, last_heartbeat, uuid, seniority_ts, inserted_at, update_reason, radio_type) + VALUES + ($1, $2, $3, $4, $5, $6, $7) + "#, + ) + .bind(hb.heartbeat.key()) + .bind(hb.heartbeat.timestamp) + .bind(hb.heartbeat.coverage_object) + .bind(ts) + .bind(ts) + .bind(proto::SeniorityUpdateReason::NewCoverageClaimTime as i32) + .bind(hb.heartbeat.hb_type) + .execute(&mut **exec) + .await?; + Ok(()) +} diff --git a/mobile_verifier/tests/integrations/hex_boosting.rs b/mobile_verifier/tests/integrations/hex_boosting.rs index 0823c336f..6589ec970 100644 --- a/mobile_verifier/tests/integrations/hex_boosting.rs +++ b/mobile_verifier/tests/integrations/hex_boosting.rs @@ -134,7 +134,7 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { let price_info = default_price_info(); - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &hex_boosting_client, mobile_rewards_client, @@ -144,6 +144,7 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); @@ -193,7 +194,9 @@ async fn test_poc_with_boosted_hexes(pool: PgPool) -> anyhow::Result<()> { // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -288,7 +291,7 @@ async fn test_poc_boosted_hexes_unique_connections_not_seeded(pool: PgPool) -> a let price_info = default_price_info(); // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &hex_boosting_client, mobile_rewards_client, @@ -298,6 +301,7 @@ async fn test_poc_boosted_hexes_unique_connections_not_seeded(pool: PgPool) -> a .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); @@ -315,7 +319,9 @@ async fn test_poc_boosted_hexes_unique_connections_not_seeded(pool: PgPool) -> a // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -417,7 +423,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res ]; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &MockHexBoostingClient::new(boosted_hexes), mobile_rewards_client, @@ -427,6 +433,7 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); @@ -487,7 +494,9 @@ async fn test_poc_with_multi_coverage_boosted_hexes(pool: PgPool) -> anyhow::Res // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -552,7 +561,7 @@ async fn test_expired_boosted_hex(pool: PgPool) -> anyhow::Result<()> { ]; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &MockHexBoostingClient::new(boosted_hexes), mobile_rewards_client, @@ -562,6 +571,7 @@ async fn test_expired_boosted_hex(pool: PgPool) -> anyhow::Result<()> { .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); @@ -583,7 +593,9 @@ async fn test_expired_boosted_hex(pool: PgPool) -> anyhow::Result<()> { // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -654,7 +666,7 @@ async fn test_reduced_location_score_with_boosted_hexes(pool: PgPool) -> anyhow: ]; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &MockHexBoostingClient::new(boosted_hexes), mobile_rewards_client, @@ -664,6 +676,7 @@ async fn test_reduced_location_score_with_boosted_hexes(pool: PgPool) -> anyhow: .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); // full location trust 1 boost @@ -715,7 +728,9 @@ async fn test_reduced_location_score_with_boosted_hexes(pool: PgPool) -> anyhow: // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -791,7 +806,7 @@ async fn test_distance_from_asserted_removes_boosting_but_not_location_trust( ]; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &MockHexBoostingClient::new(boosted_hexes), mobile_rewards_client, @@ -801,6 +816,7 @@ async fn test_distance_from_asserted_removes_boosting_but_not_location_trust( .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_amount = poc_unallocated_amount.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); // full location trust 1 boost @@ -855,7 +871,9 @@ async fn test_distance_from_asserted_removes_boosting_but_not_location_trust( // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_amount; assert_total_matches_emissions(total, &reward_info); Ok(()) @@ -954,7 +972,7 @@ async fn test_poc_with_wifi_and_multi_coverage_boosted_hexes(pool: PgPool) -> an ]; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_reward) = rewarder::reward_poc_and_dc( &pool, &MockHexBoostingClient::new(boosted_hexes), mobile_rewards_client, @@ -964,6 +982,7 @@ async fn test_poc_with_wifi_and_multi_coverage_boosted_hexes(pool: PgPool) -> an .await?; let rewards = mobile_rewards.finish().await?; + let poc_unallocated_reward = poc_unallocated_reward.to_u64().unwrap_or(0); let poc_rewards = rewards.radio_reward_v2s.as_keyed_map(); let hotspot_1 = poc_rewards.get(HOTSPOT_1).expect("hotspot 1"); // 2 boosts at 10x @@ -1023,7 +1042,9 @@ async fn test_poc_with_wifi_and_multi_coverage_boosted_hexes(pool: PgPool) -> an // confirm the total rewards allocated matches emissions // and the rewarded percentage amount matches percentage - let total = rewards.total_poc_rewards() + rewards.unallocated_amount_or_default(); + let total = rewards.total_poc_rewards() + + rewards.unallocated_amount_or_default() + + poc_unallocated_reward; assert_total_matches_emissions(total, &reward_info); Ok(()) diff --git a/mobile_verifier/tests/integrations/main.rs b/mobile_verifier/tests/integrations/main.rs index 5b4c1557e..d47b582a2 100644 --- a/mobile_verifier/tests/integrations/main.rs +++ b/mobile_verifier/tests/integrations/main.rs @@ -7,6 +7,7 @@ mod heartbeats; mod hex_boosting; mod last_location; mod modeled_coverage; +mod reward_distributor; mod rewarder_poc_dc; mod rewarder_sp_rewards; mod seniority; diff --git a/mobile_verifier/tests/integrations/reward_distributor.rs b/mobile_verifier/tests/integrations/reward_distributor.rs new file mode 100644 index 000000000..d0d44e96f --- /dev/null +++ b/mobile_verifier/tests/integrations/reward_distributor.rs @@ -0,0 +1,49 @@ +use crate::common::{ + create_file_sink, default_price_info, reward_info_24_hours, + seed::{seed_data_sessions, seed_heartbeats, update_assignments}, + MockHexBoostingClient, RadioRewardV2Ext, +}; +use mobile_verifier::reward_shares::get_scheduled_tokens_total; +use mobile_verifier::rewarder; +use sqlx::PgPool; + +#[sqlx::test] +async fn test_distribute_rewards(pool: PgPool) -> anyhow::Result<()> { + let (mobile_rewards_client, mobile_rewards) = create_file_sink(); + let reward_info = reward_info_24_hours(); + + let mut txn = pool.clone().begin().await?; + seed_heartbeats(reward_info.epoch_period.start, &mut txn).await?; + seed_data_sessions(reward_info.epoch_period.start, &mut txn).await?; + txn.commit().await?; + update_assignments(&pool).await?; + + let hex_boosting_client = MockHexBoostingClient::new(vec![]); + let price_info = default_price_info(); + + rewarder::distribute_rewards( + &pool, + &hex_boosting_client, + mobile_rewards_client, + &reward_info, + price_info, + ) + .await?; + + let rewards = mobile_rewards.finish().await?; + let poc_rewards = rewards.radio_reward_v2s; + let dc_rewards = rewards.gateway_rewards; + let sp_rewards = rewards.sp_rewards; + + let poc_sum: u64 = poc_rewards.iter().map(|r| r.total_poc_reward()).sum(); + let dc_sum: u64 = dc_rewards.iter().map(|r| r.dc_transfer_reward).sum(); + let sp_sum: u64 = sp_rewards.iter().map(|r| r.amount).sum(); + + let total: u64 = poc_sum + dc_sum + sp_sum; + + let expected_total = get_scheduled_tokens_total(reward_info.epoch_emissions); + + assert_eq!(total, expected_total); + + Ok(()) +} diff --git a/mobile_verifier/tests/integrations/rewarder_poc_dc.rs b/mobile_verifier/tests/integrations/rewarder_poc_dc.rs index 9dc12f47d..ecfc8133a 100644 --- a/mobile_verifier/tests/integrations/rewarder_poc_dc.rs +++ b/mobile_verifier/tests/integrations/rewarder_poc_dc.rs @@ -1,29 +1,24 @@ use std::ops::Range; +use crate::common::seed::{ + seed_data_sessions, seed_heartbeats, seed_speedtests, seed_unique_connections, + update_assignments, update_assignments_bad, HOTSPOT_3, +}; use crate::common::{ self, default_price_info, reward_info_24_hours, MockHexBoostingClient, RadioRewardV2Ext, }; -use chrono::{DateTime, Duration as ChronoDuration, Utc}; -use file_store_oracles::{ - coverage::{CoverageObject as FSCoverageObject, KeyType, RadioHexSignalLevel}, - mobile_ban, - speedtest::CellSpeedtest, - unique_connections::{UniqueConnectionReq, UniqueConnectionsIngestReport}, -}; +use chrono::{DateTime, Utc}; +use file_store_oracles::mobile_ban; use helium_crypto::PublicKeyBinary; +use helium_proto::services::poc_mobile::UnallocatedRewardType; use mobile_verifier::{ banning, - cell_type::CellType, - coverage::CoverageObject, - data_session, - heartbeats::{HbType, Heartbeat, ValidatedHeartbeat}, reward_shares::{self, DataTransferAndPocAllocatedRewardBuckets}, - rewarder, speedtests, unique_connections, + rewarder, }; use rust_decimal::prelude::*; use rust_decimal_macros::dec; use sqlx::{PgPool, Postgres, Transaction}; -use uuid::Uuid; pub mod proto { pub use helium_proto::services::poc_mobile::{ @@ -37,11 +32,6 @@ pub mod proto { }; } -const HOTSPOT_1: &str = "112NqN2WWMwtK29PMzRby62fDydBJfsCLkCAf392stdok48ovNT6"; -const HOTSPOT_2: &str = "11uJHS2YaEWJqgqC7yza9uvSmpv5FWoMQXiP8WbxBGgNUmifUJf"; -const HOTSPOT_3: &str = "112E7TxoNHV46M6tiPA8N1MkeMeQxc9ztb4JQLXBVAAUfq1kJLoF"; -const PAYER_1: &str = "11eX55faMbqZB7jzN4p67m6w7ScPMH6ubnvCjCPLh72J49PaJEL"; - #[sqlx::test] async fn test_poc_and_dc_rewards(pool: PgPool) -> anyhow::Result<()> { let (mobile_rewards_client, mobile_rewards) = common::create_file_sink(); @@ -60,7 +50,7 @@ async fn test_poc_and_dc_rewards(pool: PgPool) -> anyhow::Result<()> { let price_info = default_price_info(); // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_amount) = rewarder::reward_poc_and_dc( &pool, &hex_boosting_client, mobile_rewards_client, @@ -72,7 +62,6 @@ async fn test_poc_and_dc_rewards(pool: PgPool) -> anyhow::Result<()> { let rewards = mobile_rewards.finish().await?; let poc_rewards = rewards.radio_reward_v2s; let dc_rewards = rewards.gateway_rewards; - let unallocated_reward = rewards.unallocated.first(); let poc_sum: u64 = poc_rewards.iter().map(|r| r.total_poc_reward()).sum(); @@ -80,9 +69,15 @@ async fn test_poc_and_dc_rewards(pool: PgPool) -> anyhow::Result<()> { assert_eq!(poc_sum / 3, poc_rewards[1].total_poc_reward()); assert_eq!(poc_sum / 3, poc_rewards[2].total_poc_reward()); - // assert the unallocated reward - let unallocated_reward = unallocated_reward.unwrap(); - assert_eq!(unallocated_reward.amount, 1); + // assert no unallocated reward writes + assert_eq!( + rewards + .unallocated + .iter() + .filter(|r| r.reward_type == UnallocatedRewardType::Poc as i32) + .count(), + 0 + ); // assert the boosted hexes in the radio rewards // boosted hexes will contain the used multiplier for each boosted hex @@ -98,7 +93,7 @@ async fn test_poc_and_dc_rewards(pool: PgPool) -> anyhow::Result<()> { // confirm the total rewards allocated matches expectations let dc_sum: u64 = dc_rewards.iter().map(|r| r.dc_transfer_reward).sum(); - let total = poc_sum + dc_sum + unallocated_reward.amount; + let total = poc_sum + dc_sum + poc_unallocated_amount.to_u64().unwrap_or(0); let expected_sum = reward_shares::get_scheduled_tokens_for_poc(reward_info.epoch_emissions) .to_u64() @@ -119,7 +114,7 @@ async fn test_qualified_wifi_poc_rewards(pool: PgPool) -> anyhow::Result<()> { let reward_info = reward_info_24_hours(); - let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse().unwrap(); // wifi hotspot + let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse()?; // wifi hotspot // seed all the things let mut txn = pool.clone().begin().await?; @@ -190,7 +185,7 @@ async fn test_sp_banned_radio(pool: PgPool) -> anyhow::Result<()> { let reward_info = reward_info_24_hours(); - let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse().unwrap(); // wifi hotspot + let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse()?; // wifi hotspot // seed all the things let mut txn = pool.clone().begin().await?; @@ -249,7 +244,7 @@ async fn test_all_banned_radio(pool: PgPool) -> anyhow::Result<()> { let reward_info = reward_info_24_hours(); - let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse().unwrap(); // wifi hotspot + let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse()?; // wifi hotspot // seed all the things let mut txn = pool.clone().begin().await?; @@ -275,7 +270,7 @@ async fn test_all_banned_radio(pool: PgPool) -> anyhow::Result<()> { txn.commit().await?; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_reward) = rewarder::reward_poc_and_dc( &pool, &hex_boosting_client, mobile_rewards_client, @@ -286,13 +281,13 @@ async fn test_all_banned_radio(pool: PgPool) -> anyhow::Result<()> { let rewards = mobile_rewards.finish().await?; let poc_rewards = rewards.radio_reward_v2s; - let dc_rewards = rewards.gateway_rewards; + let poc_unallocated_reward = poc_unallocated_reward.to_u64().unwrap_or(0); // expecting single radio with poc rewards, minimal unallocated due to rounding assert_eq!(poc_rewards.len(), 2); assert_eq!(dc_rewards.len(), 3); - assert_eq!(rewards.unallocated.len(), 1); + assert!(poc_unallocated_reward >= 1); Ok(()) } @@ -303,7 +298,7 @@ async fn test_data_banned_radio_still_receives_poc(pool: PgPool) -> anyhow::Resu let reward_info = reward_info_24_hours(); - let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse().unwrap(); // wifi hotspot + let pubkey: PublicKeyBinary = HOTSPOT_3.to_string().parse()?; // wifi hotspot // seed all the things let mut txn = pool.clone().begin().await?; @@ -328,7 +323,7 @@ async fn test_data_banned_radio_still_receives_poc(pool: PgPool) -> anyhow::Resu txn.commit().await?; // run rewards for poc and dc - rewarder::reward_poc_and_dc( + let (_, _, poc_unallocated_reward) = rewarder::reward_poc_and_dc( &pool, &hex_boosting_client, mobile_rewards_client, @@ -340,293 +335,12 @@ async fn test_data_banned_radio_still_receives_poc(pool: PgPool) -> anyhow::Resu let rewards = mobile_rewards.finish().await?; let poc_rewards = rewards.radio_reward_v2s; let dc_rewards = rewards.gateway_rewards; + let poc_unallocated_reward = poc_unallocated_reward.to_u64().unwrap_or(0); assert_eq!(poc_rewards.len(), 3); assert_eq!(dc_rewards.len(), 0); - assert_eq!(rewards.unallocated.len(), 1); - - Ok(()) -} - -async fn seed_heartbeats( - ts: DateTime, - txn: &mut Transaction<'_, Postgres>, -) -> anyhow::Result<()> { - for n in 0..24 { - let hotspot_key1: PublicKeyBinary = HOTSPOT_1.to_string().parse().unwrap(); - let cov_obj_1 = create_coverage_object( - ts + ChronoDuration::hours(n), - hotspot_key1.clone(), - 0x8a1fb466d2dffff_u64, - true, - ); - let wifi_heartbeat_1 = ValidatedHeartbeat { - heartbeat: Heartbeat { - hb_type: HbType::Wifi, - hotspot_key: hotspot_key1, - operation_mode: true, - lat: 0.0, - lon: 0.0, - coverage_object: Some(cov_obj_1.coverage_object.uuid), - location_validation_timestamp: None, - timestamp: ts + ChronoDuration::hours(n), - location_source: proto::LocationSource::Gps, - }, - cell_type: CellType::NovaGenericWifiIndoor, - distance_to_asserted: Some(0), - coverage_meta: None, - location_trust_score_multiplier: dec!(1.0), - validity: proto::HeartbeatValidity::Valid, - }; - - let hotspot_key2: PublicKeyBinary = HOTSPOT_2.to_string().parse().unwrap(); - let cov_obj_2 = create_coverage_object( - ts + ChronoDuration::hours(n), - hotspot_key2.clone(), - 0x8a1fb49642dffff_u64, - true, - ); - let wifi_heartbeat_2 = ValidatedHeartbeat { - heartbeat: Heartbeat { - hb_type: HbType::Wifi, - hotspot_key: hotspot_key2, - operation_mode: true, - lat: 0.0, - lon: 0.0, - coverage_object: Some(cov_obj_2.coverage_object.uuid), - location_validation_timestamp: None, - timestamp: ts + ChronoDuration::hours(n), - location_source: proto::LocationSource::Gps, - }, - cell_type: CellType::NovaGenericWifiIndoor, - distance_to_asserted: Some(0), - coverage_meta: None, - location_trust_score_multiplier: dec!(1.0), - validity: proto::HeartbeatValidity::Valid, - }; - - let hotspot_key3: PublicKeyBinary = HOTSPOT_3.to_string().parse().unwrap(); - let cov_obj_3 = create_coverage_object( - ts + ChronoDuration::hours(n), - hotspot_key3.clone(), - 0x8c2681a306607ff_u64, - true, - ); - let wifi_heartbeat_3 = ValidatedHeartbeat { - heartbeat: Heartbeat { - hb_type: HbType::Wifi, - hotspot_key: hotspot_key3, - operation_mode: true, - lat: 0.0, - lon: 0.0, - coverage_object: Some(cov_obj_3.coverage_object.uuid), - location_validation_timestamp: Some(ts - ChronoDuration::hours(24)), - timestamp: ts + ChronoDuration::hours(n), - location_source: proto::LocationSource::Skyhook, - }, - cell_type: CellType::NovaGenericWifiIndoor, - distance_to_asserted: Some(0), - coverage_meta: None, - location_trust_score_multiplier: dec!(1.0), - validity: proto::HeartbeatValidity::Valid, - }; - - save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_3, txn).await?; - save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_1, txn).await?; - save_seniority_object(ts + ChronoDuration::hours(n), &wifi_heartbeat_2, txn).await?; - - wifi_heartbeat_1.save(txn).await?; - wifi_heartbeat_2.save(txn).await?; - wifi_heartbeat_3.save(txn).await?; - - cov_obj_1.save(txn).await?; - cov_obj_2.save(txn).await?; - cov_obj_3.save(txn).await?; - } - Ok(()) -} - -async fn update_assignments(pool: &PgPool) -> anyhow::Result<()> { - let _ = common::set_unassigned_oracle_boosting_assignments( - pool, - &common::mock_hex_boost_data_default(), - ) - .await?; - Ok(()) -} - -async fn update_assignments_bad(pool: &PgPool) -> anyhow::Result<()> { - let _ = common::set_unassigned_oracle_boosting_assignments( - pool, - &common::mock_hex_boost_data_bad(), - ) - .await?; - Ok(()) -} - -async fn seed_speedtests( - ts: DateTime, - txn: &mut Transaction<'_, Postgres>, -) -> anyhow::Result<()> { - for n in 0..24 { - let hotspot1_speedtest = CellSpeedtest { - pubkey: HOTSPOT_1.parse().unwrap(), - serial: "serial1".to_string(), - timestamp: ts - ChronoDuration::hours(n * 4), - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 50, - }; - - let hotspot2_speedtest = CellSpeedtest { - pubkey: HOTSPOT_2.parse().unwrap(), - serial: "serial2".to_string(), - timestamp: ts - ChronoDuration::hours(n * 4), - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 50, - }; - - let hotspot3_speedtest = CellSpeedtest { - pubkey: HOTSPOT_3.parse().unwrap(), - serial: "serial3".to_string(), - timestamp: ts - ChronoDuration::hours(n * 4), - upload_speed: 100_000_000, - download_speed: 100_000_000, - latency: 50, - }; - - speedtests::save_speedtest(&hotspot1_speedtest, txn).await?; - speedtests::save_speedtest(&hotspot2_speedtest, txn).await?; - speedtests::save_speedtest(&hotspot3_speedtest, txn).await?; - } - Ok(()) -} + assert!(poc_unallocated_reward >= 1); -async fn seed_data_sessions( - ts: DateTime, - txn: &mut Transaction<'_, Postgres>, -) -> anyhow::Result { - let rewardable_bytes_1 = 1_024 * 1_000; - let data_session_1 = data_session::HotspotDataSession { - pub_key: HOTSPOT_1.parse().unwrap(), - payer: PAYER_1.parse().unwrap(), - upload_bytes: 1_024 * 1_000, - download_bytes: 1_024 * 50_000, - // Here to test that rewardable_bytes is the one taken into account we lower it - rewardable_bytes: rewardable_bytes_1, - num_dcs: 5_000_000, - received_timestamp: ts + ChronoDuration::hours(1), - burn_timestamp: ts + ChronoDuration::hours(1), - }; - - let rewardable_bytes_2 = 1_024 * 1_000 + 1_024 * 50_000; - let data_session_2 = data_session::HotspotDataSession { - pub_key: HOTSPOT_2.parse().unwrap(), - payer: PAYER_1.parse().unwrap(), - upload_bytes: 1_024 * 1_000, - download_bytes: 1_024 * 50_000, - rewardable_bytes: rewardable_bytes_2, - num_dcs: 5_000_000, - received_timestamp: ts + ChronoDuration::hours(1), - burn_timestamp: ts + ChronoDuration::hours(1), - }; - - let rewardable_bytes_3 = 1_024 * 1_000 + 1_024 * 50_000; - let data_session_3 = data_session::HotspotDataSession { - pub_key: HOTSPOT_3.parse().unwrap(), - payer: PAYER_1.parse().unwrap(), - upload_bytes: 1_024 * 1_000, - download_bytes: 1_024 * 50_000, - rewardable_bytes: rewardable_bytes_3, - num_dcs: 5_000_000, - received_timestamp: ts + ChronoDuration::hours(1), - burn_timestamp: ts + ChronoDuration::hours(1), - }; - data_session_1.save(txn).await?; - data_session_2.save(txn).await?; - data_session_3.save(txn).await?; - - let rewardable = rewardable_bytes_1 + rewardable_bytes_2 + rewardable_bytes_3; - - Ok(rewardable as u64) -} - -async fn seed_unique_connections( - txn: &mut Transaction<'_, Postgres>, - things: &[(PublicKeyBinary, u64)], - epoch: &Range>, -) -> anyhow::Result<()> { - let mut reports = vec![]; - for (pubkey, unique_connections) in things { - reports.push(UniqueConnectionsIngestReport { - received_timestamp: epoch.start + chrono::Duration::hours(1), - report: UniqueConnectionReq { - pubkey: pubkey.clone(), - start_timestamp: Utc::now(), - end_timestamp: Utc::now(), - unique_connections: *unique_connections, - timestamp: Utc::now(), - carrier_key: pubkey.clone(), - signature: vec![], - }, - }); - } - unique_connections::db::save(txn, &reports).await?; - Ok(()) -} - -fn create_coverage_object( - ts: DateTime, - pub_key: PublicKeyBinary, - hex: u64, - indoor: bool, -) -> CoverageObject { - let location = h3o::CellIndex::try_from(hex).unwrap(); - let key_type = KeyType::HotspotKey(pub_key.clone()); - let report = FSCoverageObject { - pub_key, - uuid: Uuid::new_v4(), - key_type, - coverage_claim_time: ts, - coverage: vec![RadioHexSignalLevel { - location, - signal_level: proto::SignalLevel::High, - signal_power: 1000, - }], - indoor, - trust_score: 1000, - signature: Vec::new(), - }; - CoverageObject { - coverage_object: report, - validity: proto::CoverageObjectValidity::Valid, - } -} - -//TODO: use existing save methods instead of manual sql -async fn save_seniority_object( - ts: DateTime, - hb: &ValidatedHeartbeat, - exec: &mut Transaction<'_, Postgres>, -) -> anyhow::Result<()> { - sqlx::query( - r#" - INSERT INTO seniority - (radio_key, last_heartbeat, uuid, seniority_ts, inserted_at, update_reason, radio_type) - VALUES - ($1, $2, $3, $4, $5, $6, $7) - "#, - ) - .bind(hb.heartbeat.key()) - .bind(hb.heartbeat.timestamp) - .bind(hb.heartbeat.coverage_object) - .bind(ts) - .bind(ts) - .bind(proto::SeniorityUpdateReason::NewCoverageClaimTime as i32) - .bind(hb.heartbeat.hb_type) - .execute(&mut **exec) - .await?; Ok(()) } @@ -708,7 +422,7 @@ async fn test_reward_poc_with_zero_poc_allocation(pool: PgPool) -> anyhow::Resul let reward_shares = DataTransferAndPocAllocatedRewardBuckets::new(dec!(1000000)); // Test the reward_poc function directly with zero POC allocation - let (unallocated_amount, _calculated_shares) = rewarder::reward_poc( + let (_calculated_shares, _allocated_amount, unallocated_amount) = rewarder::reward_poc( &pool, &hex_boosting_client, &mobile_rewards_client, diff --git a/mobile_verifier/tests/integrations/rewarder_sp_rewards.rs b/mobile_verifier/tests/integrations/rewarder_sp_rewards.rs index fb1d8782d..c5cbff21f 100644 --- a/mobile_verifier/tests/integrations/rewarder_sp_rewards.rs +++ b/mobile_verifier/tests/integrations/rewarder_sp_rewards.rs @@ -1,6 +1,8 @@ use crate::common::{self, reward_info_24_hours}; use helium_proto::{services::poc_mobile::UnallocatedRewardType, ServiceProvider}; -use mobile_verifier::reward_shares::RewardableEntityKey; +use mobile_verifier::reward_shares::{ + get_scheduled_tokens_for_poc, get_scheduled_tokens_total, RewardableEntityKey, +}; use mobile_verifier::{reward_shares, rewarder}; use rust_decimal::prelude::*; use rust_decimal_macros::dec; @@ -11,8 +13,15 @@ async fn test_service_provider_rewards(_pool: PgPool) -> anyhow::Result<()> { let (mobile_rewards_client, mobile_rewards) = common::create_file_sink(); let reward_info = reward_info_24_hours(); + let poc_allocated_amount = get_scheduled_tokens_for_poc(reward_info.epoch_emissions) + .to_u64() + .unwrap_or(0); - rewarder::reward_service_providers(mobile_rewards_client, &reward_info).await?; + let total_rewards = get_scheduled_tokens_total(reward_info.epoch_emissions); + let sp_reward_amount = total_rewards - poc_allocated_amount; + + rewarder::reward_service_providers(mobile_rewards_client, &reward_info, sp_reward_amount) + .await?; let rewards = mobile_rewards.finish().await?; @@ -79,7 +88,15 @@ async fn should_not_reward_service_provider_negative_amount(_pool: PgPool) -> an // Total reward amount of 350 HNT reward_info.epoch_emissions = Decimal::from(35_000_000_000u64); - rewarder::reward_service_providers(mobile_rewards_client, &reward_info).await?; + let poc_allocated_amount = get_scheduled_tokens_for_poc(reward_info.epoch_emissions) + .to_u64() + .unwrap_or(0); + + let total_rewards = get_scheduled_tokens_total(reward_info.epoch_emissions); + let sp_reward_amount = total_rewards - poc_allocated_amount; + + rewarder::reward_service_providers(mobile_rewards_client, &reward_info, sp_reward_amount) + .await?; let rewards = mobile_rewards.finish().await?;