Skip to content

Commit

Permalink
Include share values in rewards manifest (#827)
Browse files Browse the repository at this point in the history
* output share values in rewards manifest for iot and mobile

* tweaks

* fmt

* Store coverage points received by boosting separately

This is preparing for being able to make sure boosted rewards do not take more than 10% of the reward shares.

base_coverage_points will always be recieved, but boosted_coverage_points may receive less rewards per share depending on how many there are in a reward epoch.

Note that boosted_coverage_points _do not_ contain base_coverage_points.

* Add struct for containing allocated Rewards for Data transfer and POC

If boosted rewards are above the allowed 10% of total emissions, the rewards per share needs to be recalculated _not including_ leftover DC from Data Transfer rewards. 

To make this easier, we store data transfer allocated rewards and resulting unallocated rewards separately from the 10% buckets for poc and boosting.

* Move calucating reward shares for poc to coverage point calculator

* Correct points for share calculation alway includes multipliers

Points considered when doing math for shares for a radio should always include speedtest and location multipliers.

* Use the AllocatedRewardShare struct for calculating unallocated data transfer rewards

* Add caller to timeout message for file sink during debugging

* Fix first hex_boosting intregration test

- Add a way for receiving rewards from the mock sink to determine if it needs to await for an unallocated msg.

- Derive the expected rewards from math outside the oracles repo

* Fix second hex_boosting integration test

This test really illustrates the preference for coverage over boosting. A hotspot covering 2 hexes can far surpass a hotspot covering a single hex with a strong boost multiplier.

* Fix third hex_boosting integration test

This tests math looks different because it's the first time we're dealing with different coverage point values from radios because of degraded location trust scores.

* Fix fourth hex_boosting integration test

This test almost exactly the same as `test_poc_with_multi_coverage_boosted_hexes` but the last radio is CBRS instead of a WIFI with a degraded location score. Interestingly, the coverage points work out to the same values between the differences.

* Use new proto branch

* Overview all changed tests

- Break out getting the poc allocation bucket sizes.
- Make sure math in comments checks out.
- Try to make the math for rewards look similar enough to feel good.

* Move reward share calculation where it belongs

- coverage_point_calculator -> reward_shares as it has to do directly with reward shares, and is not about calculating coverage points.
- AllocatedRewardShares -> DataTransferAndPocAllocatedRewardBuckets

* Add doc for CoveragePoints::coverage_points

* remove whitespace

* Unwrap calculating expected rewards in tests

* Update formatting of comment for Doc.rs readability

* Fix broken doclink

- `HexPoints` to public so the doc comment can be seem

* fully expose `HexPoints` members

bumper rails retracting

* Add reward data to manifest struct

* remove public from helper member

* rename points -> shares for poc rewards

There will soon be a mobile rewards v2 proto. In which we better define
what a "point" means. It was considered that Location Trust was part of
a "coverage", that will no longer be the case.

Points are the base value provided from covering a Hex, a "coverage
point" if you will.

Combining those points with location and speedtest multipliers, both
things having to with the radio directly, we arrive at "shares".

* coverage_points() -> coverage_points_v1() to call out location difference

coverage points including the location trust multiplier, but not the
speedtest multiplier has been a source of confusion. Shortly, that will
no longer be the case.

This method has been renamed to raise an eyebrow as to why it needs a v1
specifier.

* Update to latest

* Update proto

* I guess this is more correct?

* Rename fields to be more accurate

* Update proto to master

* Annoying

---------

Co-authored-by: Michael Jeffrey <michaeldjeffrey@gmail.com>
Co-authored-by: Matthew Plant <maplant@protonmail.com>
Co-authored-by: Matthew Plant <matty@nova-labs.com>
  • Loading branch information
4 people authored Jul 11, 2024
1 parent ba2615e commit fe83fa3
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 53 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions file_store/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ pub enum DecodeError {
Uri(#[from] http::uri::InvalidUri),
#[error("integer conversion error")]
FromInt(#[from] std::num::TryFromIntError),
#[error("error parsing decimal")]
IntoDecimal(#[from] rust_decimal::Error),
#[error("empty field: {0}")]
EmptyField(&'static str),
#[error("unsupported region, type: {0}, value: {1}")]
UnsupportedRegion(String, i32),
#[error("unsupported datarate, type: {0}, value: {1}")]
Expand Down Expand Up @@ -166,6 +170,10 @@ impl DecodeError {
pub fn unsupported_invalidated_reason<E: ToString>(msg1: E, msg2: i32) -> Error {
Error::Decode(Self::UnsupportedInvalidReason(msg1.to_string(), msg2))
}

pub const fn empty_field(field: &'static str) -> Error {
Error::Decode(Self::EmptyField(field))
}
}

impl From<helium_crypto::Error> for Error {
Expand Down
62 changes: 62 additions & 0 deletions file_store/src/reward_manifest.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
use crate::{error::DecodeError, traits::MsgDecode, Error};
use chrono::{DateTime, TimeZone, Utc};
use helium_proto as proto;
use rust_decimal::Decimal;

#[derive(Clone, Debug)]
pub struct RewardManifest {
pub written_files: Vec<String>,
pub start_timestamp: DateTime<Utc>,
pub end_timestamp: DateTime<Utc>,
pub reward_data: Option<RewardData>,
}

#[derive(Clone, Debug)]
pub enum RewardData {
MobileRewardData {
poc_bones_per_coverage_point: Decimal,
boosted_poc_bones_per_coverage_point: Decimal,
},
IotRewardData {
poc_bones_per_beacon_reward_share: Decimal,
poc_bones_per_witness_reward_share: Decimal,
dc_bones_per_share: Decimal,
},
}

impl MsgDecode for RewardManifest {
Expand All @@ -31,6 +46,53 @@ impl TryFrom<proto::RewardManifest> for RewardManifest {
.ok_or(Error::Decode(DecodeError::InvalidTimestamp(
value.end_timestamp,
)))?,
reward_data: match value.reward_data {
Some(proto::reward_manifest::RewardData::MobileRewardData(reward_data)) => {
Some(RewardData::MobileRewardData {
poc_bones_per_coverage_point: reward_data
.poc_bones_per_reward_share
.ok_or(DecodeError::empty_field("poc_bones_per_coverage_point"))?
.value
.parse()
.map_err(DecodeError::from)?,
boosted_poc_bones_per_coverage_point: reward_data
.boosted_poc_bones_per_reward_share
.ok_or(DecodeError::empty_field(
"boosted_poc_bones_per_coverage_point",
))?
.value
.parse()
.map_err(DecodeError::from)?,
})
}
Some(proto::reward_manifest::RewardData::IotRewardData(reward_data)) => {
Some(RewardData::IotRewardData {
poc_bones_per_beacon_reward_share: reward_data
.poc_bones_per_beacon_reward_share
.ok_or(DecodeError::empty_field(
"poc_bones_per_beacon_reward_share",
))?
.value
.parse()
.map_err(DecodeError::from)?,
poc_bones_per_witness_reward_share: reward_data
.poc_bones_per_witness_reward_share
.ok_or(DecodeError::empty_field(
"poc_bones_per_witness_reward_share",
))?
.value
.parse()
.map_err(DecodeError::from)?,
dc_bones_per_share: reward_data
.dc_bones_per_share
.ok_or(DecodeError::empty_field("dc_bones_per_share"))?
.value
.parse()
.map_err(DecodeError::from)?,
})
}
None => None,
},
})
}
}
42 changes: 34 additions & 8 deletions iot_verifier/src/rewarder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use chrono::{DateTime, TimeZone, Utc};
use db_store::meta;
use file_store::{file_sink, traits::TimestampEncode};
use futures::future::LocalBoxFuture;
use helium_proto::services::poc_lora as proto;
use helium_proto::services::poc_lora::iot_reward_share::Reward as ProtoReward;
use helium_proto::services::poc_lora::{UnallocatedReward, UnallocatedRewardType};
use helium_proto::RewardManifest;
use helium_proto::{
reward_manifest::RewardData::IotRewardData,
services::poc_lora::{
self as proto, iot_reward_share::Reward as ProtoReward, UnallocatedReward,
UnallocatedRewardType,
},
IotRewardData as ManifestIotRewardData, RewardManifest,
};
use humantime_serde::re::humantime;
use price::PriceTracker;
use reward_scheduler::Scheduler;
Expand All @@ -31,6 +35,12 @@ pub struct Rewarder {
pub price_tracker: PriceTracker,
}

pub struct RewardPocDcDataPoints {
beacon_rewards_per_share: Decimal,
witness_rewards_per_share: Decimal,
dc_transfer_rewards_per_share: Decimal,
}

impl ManagedTask for Rewarder {
fn start_task(
self: Box<Self>,
Expand Down Expand Up @@ -116,7 +126,8 @@ impl Rewarder {
let reward_period = &scheduler.reward_period;

// process rewards for poc and dc
reward_poc_and_dc(&self.pool, &self.rewards_sink, reward_period, iot_price).await?;
let poc_dc_shares =
reward_poc_and_dc(&self.pool, &self.rewards_sink, reward_period, iot_price).await?;
// process rewards for the operational fund
reward_operational(&self.rewards_sink, reward_period).await?;
// process rewards for the oracle
Expand Down Expand Up @@ -145,12 +156,24 @@ impl Rewarder {
transaction.commit().await?;

// now that the db has been purged, safe to write out the manifest
let reward_data = ManifestIotRewardData {
poc_bones_per_beacon_reward_share: Some(helium_proto::Decimal {
value: poc_dc_shares.beacon_rewards_per_share.to_string(),
}),
poc_bones_per_witness_reward_share: Some(helium_proto::Decimal {
value: poc_dc_shares.witness_rewards_per_share.to_string(),
}),
dc_bones_per_share: Some(helium_proto::Decimal {
value: poc_dc_shares.dc_transfer_rewards_per_share.to_string(),
}),
};
self.reward_manifests_sink
.write(
RewardManifest {
start_timestamp: scheduler.reward_period.start.encode_timestamp(),
end_timestamp: scheduler.reward_period.end.encode_timestamp(),
written_files,
reward_data: Some(IotRewardData(reward_data)),
},
[],
)
Expand Down Expand Up @@ -205,13 +228,12 @@ impl Rewarder {
.ok_or(db_store::Error::DecodeError)
}
}

pub async fn reward_poc_and_dc(
pool: &Pool<Postgres>,
rewards_sink: &file_sink::FileSinkClient,
reward_period: &Range<DateTime<Utc>>,
iot_price: Decimal,
) -> anyhow::Result<()> {
) -> anyhow::Result<RewardPocDcDataPoints> {
let reward_shares = reward_share::aggregate_reward_shares(pool, reward_period).await?;
let gateway_shares = GatewayShares::new(reward_shares)?;
let (beacon_rewards_per_share, witness_rewards_per_share, dc_transfer_rewards_per_share) =
Expand Down Expand Up @@ -254,7 +276,11 @@ pub async fn reward_poc_and_dc(
reward_period,
)
.await?;
Ok(())
Ok(RewardPocDcDataPoints {
beacon_rewards_per_share,
witness_rewards_per_share,
dc_transfer_rewards_per_share,
})
}

pub async fn reward_operational(
Expand Down
3 changes: 2 additions & 1 deletion mobile_verifier/src/cli/reward_from_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ impl Cmd {
DataTransferAndPocAllocatedRewardBuckets::new(&epoch),
&epoch,
)
.ok_or(anyhow::anyhow!("no rewardable events"))?;
.ok_or(anyhow::anyhow!("no rewardable events"))?
.1;
for (_reward_amount, reward) in radio_rewards {
if let Some(proto::mobile_reward_share::Reward::RadioReward(proto::RadioReward {
hotspot_key,
Expand Down
25 changes: 18 additions & 7 deletions mobile_verifier/src/reward_shares.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,10 @@ impl CoverageShares {
self,
reward_shares: DataTransferAndPocAllocatedRewardBuckets,
epoch: &'_ Range<DateTime<Utc>>,
) -> Option<impl Iterator<Item = (u64, proto::MobileRewardShare)> + '_> {
) -> Option<(
CalculatedPocRewardShares,
impl Iterator<Item = (u64, proto::MobileRewardShare)> + '_,
)> {
struct ProcessedRadio {
radio_id: RadioId,
points: coverage_point_calculator::CoveragePoints,
Expand Down Expand Up @@ -615,7 +618,8 @@ impl CoverageShares {
return None;
};

Some(
Some((
rewards_per_share,
processed_radios
.into_iter()
.map(move |radio| {
Expand All @@ -638,7 +642,7 @@ impl CoverageShares {
(poc_reward, mobile_reward_share)
})
.filter(|(poc_reward, _mobile_reward)| *poc_reward > 0),
)
))
}

/// Only used for testing
Expand Down Expand Up @@ -701,10 +705,10 @@ impl DataTransferAndPocAllocatedRewardBuckets {
/// Reference:
/// HIP 122: Amend Service Provider Hex Boosting
/// https://github.com/helium/HIP/blob/main/0122-amend-service-provider-hex-boosting.md
#[derive(Debug)]
struct CalculatedPocRewardShares {
normal: Decimal,
boost: Decimal,
#[derive(Copy, Clone, Debug, Default)]
pub struct CalculatedPocRewardShares {
pub(crate) normal: Decimal,
pub(crate) boost: Decimal,
}

impl CalculatedPocRewardShares {
Expand Down Expand Up @@ -1499,6 +1503,7 @@ mod test {
.unwrap()
.into_rewards(reward_shares, &epoch)
.unwrap()
.1
{
let radio_reward = match mobile_reward.reward {
Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward,
Expand Down Expand Up @@ -1673,6 +1678,7 @@ mod test {
.unwrap()
.into_rewards(reward_shares, &epoch)
.unwrap()
.1
{
let radio_reward = match mobile_reward.reward {
Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward,
Expand Down Expand Up @@ -1804,6 +1810,7 @@ mod test {
.unwrap()
.into_rewards(reward_shares, &epoch)
.unwrap()
.1
{
let radio_reward = match mobile_reward.reward {
Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward,
Expand Down Expand Up @@ -1935,6 +1942,7 @@ mod test {
.unwrap()
.into_rewards(reward_shares, &epoch)
.unwrap()
.1
{
let radio_reward = match mobile_reward.reward {
Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward,
Expand Down Expand Up @@ -1976,6 +1984,8 @@ mod test {
.expect("failed gw2 parse");

let now = Utc::now();
// We should never see any radio shares from owner2, since all of them are
// less than or equal to zero.
let epoch = now - Duration::hours(1)..now;

let uuid_1 = Uuid::new_v4();
Expand Down Expand Up @@ -2074,6 +2084,7 @@ mod test {
for (_reward_amount, mobile_reward) in coverage_shares
.into_rewards(reward_shares, &epoch)
.expect("rewards output")
.1
{
let radio_reward = match mobile_reward.reward {
Some(proto::mobile_reward_share::Reward::RadioReward(radio_reward)) => radio_reward,
Expand Down
Loading

0 comments on commit fe83fa3

Please sign in to comment.