Skip to content

Commit

Permalink
HIP-122 restrict boosting from poc bucket (#836)
Browse files Browse the repository at this point in the history
* 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.

* 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

* 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.

* Round POC rewards individually

Despite talking about POC rewards as a combined number, this HIP pulls
them from seperate buckets. Because the sources of rewards are seperate,
the end values should be treated as discretely.

* Round POC rewards individually

Despite talking about POC rewards as a combined number, this HIP pulls
them from seperate buckets. Because the sources of rewards are seperate,
the end values should be treated as discretely.

- make sure we wrap the math to be unambiguous that we're rounding the
result to u64.
  • Loading branch information
michaeldjeffrey authored Jul 9, 2024
1 parent 13233e0 commit 5606471
Show file tree
Hide file tree
Showing 8 changed files with 808 additions and 433 deletions.
66 changes: 55 additions & 11 deletions coverage_point_calculator/src/hexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,46 @@ use rust_decimal_macros::dec;

use crate::{BoostedHexStatus, RadioType, Result};

/// Breakdown of points for a hex.
///
/// Example:
/// - Outdoor Wifi
/// - 1 hex boosted at `5x`
/// - Rank 2
/// - Assignment: `AAA`
/// <pre>
/// SplitPoints {
/// modeled: 16,
/// base: 8,
/// boosted: 32
/// }
/// </pre>
/// Rank 2 splits modeled points in half.
/// Boost at `5x` adds 32 points `( (8 * 5) - 8 )`
#[derive(Debug, Default, Clone)]
pub struct HexPoints {
/// Default points received for hex
///
/// (RadioType, SignalLevel) points
///
/// This is a convenience field for debugging, hexes can reach similar
/// values through different means, it helps to know the starting value.
modeled: Decimal,
/// Points including Coverage affected multipliers
///
/// modeled + (Rank * Assignment)
pub base: Decimal,
/// Points _over_ normal received from hex boosting.
///
/// (base * Boost multiplier) - base
pub boosted: Decimal,
}

#[derive(Debug, Clone)]
pub struct CoveredHex {
pub hex: hextree::Cell,
/// Default points received from (RadioType, SignalLevel) pair.
pub base_coverage_points: Decimal,
/// Coverage points including assignment, rank, and boosted hex multipliers.
pub calculated_coverage_points: Decimal,
/// Breakdown of points for a hex
pub points: HexPoints,
/// Oracle boosted Assignments
pub assignments: HexAssignments,
pub assignment_multiplier: Decimal,
Expand All @@ -32,7 +65,7 @@ pub(crate) fn clean_covered_hexes(
let covered_hexes = ranked_coverage
.into_iter()
.map(|ranked| {
let base_coverage_points = radio_type.base_coverage_points(&ranked.signal_level)?;
let modeled_coverage_points = radio_type.base_coverage_points(&ranked.signal_level)?;
let rank_multiplier = radio_type.rank_multiplier(ranked.rank);

let boosted_multiplier = if boosted_hex_status.is_eligible() {
Expand All @@ -49,15 +82,23 @@ pub(crate) fn clean_covered_hexes(
ranked.assignments.boosting_multiplier()
};

let calculated_coverage_points = base_coverage_points
let base_coverage_points =
modeled_coverage_points * assignment_multiplier * rank_multiplier;

let calculated_coverage_points = modeled_coverage_points
* assignment_multiplier
* rank_multiplier
* boosted_multiplier.unwrap_or(dec!(1));

let boosted_coverage_points = calculated_coverage_points - base_coverage_points;

Ok(CoveredHex {
hex: ranked.hex,
base_coverage_points,
calculated_coverage_points,
points: HexPoints {
modeled: modeled_coverage_points,
base: base_coverage_points,
boosted: boosted_coverage_points,
},
assignments: ranked.assignments,
assignment_multiplier,
rank: ranked.rank,
Expand All @@ -70,11 +111,14 @@ pub(crate) fn clean_covered_hexes(
Ok(covered_hexes)
}

pub(crate) fn calculated_coverage_points(covered_hexes: &[CoveredHex]) -> Decimal {
pub(crate) fn calculated_coverage_points(covered_hexes: &[CoveredHex]) -> HexPoints {
covered_hexes
.iter()
.map(|hex| hex.calculated_coverage_points)
.sum()
.fold(HexPoints::default(), |acc, hex| HexPoints {
modeled: acc.modeled + hex.points.modeled,
base: acc.base + hex.points.base,
boosted: acc.boosted + hex.points.boosted,
})
}

#[cfg(test)]
Expand Down
127 changes: 86 additions & 41 deletions coverage_point_calculator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! place to start.
//!
//! ## Important Fields
//! - [CoveredHex::base_coverage_points]
//! - [CoveredHex::points]
//! - [HIP-74][modeled-coverage]
//! - reduced cbrs radio coverage points [HIP-113][cbrs-experimental]
//!
Expand Down Expand Up @@ -55,7 +55,7 @@
//! [boosted-hex-restriction]: https://github.com/helium/oracles/pull/808
//!
pub use crate::{
hexes::CoveredHex,
hexes::{CoveredHex, HexPoints},
location::LocationTrust,
speedtest::{BytesPs, Speedtest, SpeedtestTier},
};
Expand Down Expand Up @@ -88,22 +88,29 @@ pub enum Error {
///
/// - When a radio is not eligible for boosted hex rewards, [CoveragePoints::covered_hexes] will
/// have no boosted_multiplier values.
///
/// #### Terminology
///
/// *Points*
///
/// The value provided from covering hexes. These include hex related modifiers.
/// - Rank
/// - Oracle boosting.
///
/// *Multipliers*
///
/// Values relating to a radio that modify it's points.
/// - Location Trust
/// - Speedtest
///
/// *Shares*
///
/// The result of multiplying Points and Multipliers together.
///
#[derive(Debug, Clone)]
pub struct CoveragePoints {
/// Total Rewards Shares earned by the Radio.
///
/// Includes Coverage and Backhaul.
/// Hex Coverage points * location trust multiplier * speedtest trust multiplier
pub reward_shares: Decimal,
/// Total Points of Coverage for a Radio.
///
/// Does not include Backhaul.
/// Hex coverage points * location trust multiplier
pub total_coverage_points: Decimal,
/// Coverage Points collected from each Covered Hex
///
/// Before location trust multiplier is applied.
pub hex_coverage_points: Decimal,
/// Breakdown of coverage points by source
pub coverage_points: HexPoints,
/// Location Trust Multiplier, maximum of 1
///
/// Coverage trust of a Radio
Expand Down Expand Up @@ -147,13 +154,8 @@ impl CoveragePoints {
let speedtests = speedtest::clean_speedtests(speedtests);
let speedtest_multiplier = speedtest::multiplier(&speedtests);

let reward_shares = hex_coverage_points * location_trust_multiplier * speedtest_multiplier;
let total_coverage_points = hex_coverage_points * location_trust_multiplier;

Ok(CoveragePoints {
reward_shares,
total_coverage_points,
hex_coverage_points,
coverage_points: hex_coverage_points,
location_trust_multiplier,
speedtest_multiplier,
radio_type,
Expand All @@ -164,6 +166,49 @@ impl CoveragePoints {
covered_hexes,
})
}

/// Accumulated points related only to coverage.
/// (Hex * Rank * Assignment) * Location Trust
/// Used for reporting.
///
/// NOTE:
/// Coverage Points includes Location Trust multiplier. In a future version,
/// coverage points will refer only to points received by covering a hex,
/// and multipliers like location trust will be applied later to reach a
/// value referred to as "shares".
///
/// Ref:
/// https://github.com/helium/proto/blob/master/src/service/poc_mobile.proto
/// `message radio_reward`
pub fn coverage_points_v1(&self) -> Decimal {
let total_coverage_points = self.coverage_points.base + self.boosted_points();
total_coverage_points * self.location_trust_multiplier
}

/// Accumulated points related to entire radio.
/// coverage points * speedtest
/// Used in calculating rewards
pub fn total_shares(&self) -> Decimal {
self.total_base_shares() + self.total_boosted_shares()
}

/// Useful for grabbing only base points when calculating reward shares
pub fn total_base_shares(&self) -> Decimal {
self.coverage_points.base * self.speedtest_multiplier * self.location_trust_multiplier
}

/// Useful for grabbing only boost points when calculating reward shares
pub fn total_boosted_shares(&self) -> Decimal {
self.boosted_points() * self.speedtest_multiplier * self.location_trust_multiplier
}

fn boosted_points(&self) -> Decimal {
match self.boosted_hex_eligibility {
BoostedHexStatus::Eligible => self.coverage_points.boosted,
BoostedHexStatus::WifiLocationScoreBelowThreshold(_) => dec!(0),
BoostedHexStatus::RadioThresholdNotMet => dec!(0),
}
}
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -314,7 +359,7 @@ mod tests {

// A Hex with the worst possible oracle boosting assignment.
// The boosting assignment multiplier will be 1x when the hex is provider boosted.
assert_eq!(expected_points, wifi.total_coverage_points);
assert_eq!(expected_points, wifi.coverage_points_v1());
}

#[test]
Expand Down Expand Up @@ -345,12 +390,12 @@ mod tests {
// Radio meeting the threshold is eligible for boosted hexes.
// Boosted hex provides radio with more than base_points.
let verified_wifi = calculate_wifi(RadioThreshold::Verified);
assert_eq!(base_points * dec!(5), verified_wifi.total_coverage_points);
assert_eq!(base_points * dec!(5), verified_wifi.coverage_points_v1());

// Radio not meeting the threshold is not eligible for boosted hexes.
// Boost from hex is not applied, radio receives base points.
let unverified_wifi = calculate_wifi(RadioThreshold::Unverified);
assert_eq!(base_points, unverified_wifi.total_coverage_points);
assert_eq!(base_points, unverified_wifi.coverage_points_v1());
}

#[test]
Expand Down Expand Up @@ -382,13 +427,13 @@ mod tests {
// Boosted hex provides radio with more than base_points.
let trusted_wifi = calculate_wifi(location_trust_with_scores(&[dec!(1), dec!(1)]));
assert!(trusted_wifi.location_trust_multiplier > dec!(0.75));
assert!(trusted_wifi.total_coverage_points > base_points);
assert!(trusted_wifi.coverage_points_v1() > base_points);

// Radio with poor trust score is not eligible for boosted hexes.
// Boost from hex is not applied, and points are further lowered by poor trust score.
let untrusted_wifi = calculate_wifi(location_trust_with_scores(&[dec!(0.1), dec!(0.2)]));
assert!(untrusted_wifi.location_trust_multiplier < dec!(0.75));
assert!(untrusted_wifi.total_coverage_points < base_points);
assert!(untrusted_wifi.coverage_points_v1() < base_points);
}

#[test]
Expand Down Expand Up @@ -419,7 +464,7 @@ mod tests {
let indoor_cbrs = calculate_indoor_cbrs(speedtest_maximum());
assert_eq!(
base_coverage_points * SpeedtestTier::Good.multiplier(),
indoor_cbrs.reward_shares
indoor_cbrs.total_shares()
);

let indoor_cbrs = calculate_indoor_cbrs(vec![
Expand All @@ -428,7 +473,7 @@ mod tests {
]);
assert_eq!(
base_coverage_points * SpeedtestTier::Acceptable.multiplier(),
indoor_cbrs.reward_shares
indoor_cbrs.total_shares()
);

let indoor_cbrs = calculate_indoor_cbrs(vec![
Expand All @@ -437,7 +482,7 @@ mod tests {
]);
assert_eq!(
base_coverage_points * SpeedtestTier::Degraded.multiplier(),
indoor_cbrs.reward_shares
indoor_cbrs.total_shares()
);

let indoor_cbrs = calculate_indoor_cbrs(vec![
Expand All @@ -446,7 +491,7 @@ mod tests {
]);
assert_eq!(
base_coverage_points * SpeedtestTier::Poor.multiplier(),
indoor_cbrs.reward_shares
indoor_cbrs.total_shares()
);

let indoor_cbrs = calculate_indoor_cbrs(vec![
Expand All @@ -455,7 +500,7 @@ mod tests {
]);
assert_eq!(
base_coverage_points * SpeedtestTier::Fail.multiplier(),
indoor_cbrs.reward_shares
indoor_cbrs.total_shares()
);
}

Expand Down Expand Up @@ -526,7 +571,7 @@ mod tests {
)
.expect("indoor cbrs");

assert_eq!(dec!(1073), indoor_cbrs.total_coverage_points);
assert_eq!(dec!(1073), indoor_cbrs.coverage_points_v1());
}

#[rstest]
Expand Down Expand Up @@ -556,7 +601,7 @@ mod tests {
)
.expect("outdoor wifi");

assert_eq!(expected_points, outdoor_wifi.total_coverage_points);
assert_eq!(expected_points, outdoor_wifi.coverage_points_v1());
}

#[rstest]
Expand Down Expand Up @@ -605,7 +650,7 @@ mod tests {
)
.expect("indoor wifi");

assert_eq!(expected_points, indoor_wifi.total_coverage_points);
assert_eq!(expected_points, indoor_wifi.coverage_points_v1());
}

#[test]
Expand All @@ -630,7 +675,7 @@ mod tests {

// Location trust scores is 1/4
// (0.1 + 0.2 + 0.3 + 0.4) / 4
assert_eq!(dec!(100), indoor_wifi.total_coverage_points);
assert_eq!(dec!(100), indoor_wifi.coverage_points_v1());
}

#[test]
Expand Down Expand Up @@ -666,7 +711,7 @@ mod tests {

// The hex with a low signal_level is boosted to the same level as a
// signal_level of High.
assert_eq!(dec!(800), indoor_wifi.total_coverage_points);
assert_eq!(dec!(800), indoor_wifi.coverage_points_v1());
}

#[rstest]
Expand Down Expand Up @@ -695,7 +740,7 @@ mod tests {
)
.expect("outdoor cbrs");

assert_eq!(expected, outdoor_cbrs.total_coverage_points);
assert_eq!(expected, outdoor_cbrs.coverage_points_v1());
}

#[rstest]
Expand All @@ -722,7 +767,7 @@ mod tests {
)
.expect("indoor cbrs");

assert_eq!(expected, indoor_cbrs.total_coverage_points);
assert_eq!(expected, indoor_cbrs.coverage_points_v1());
}

#[rstest]
Expand Down Expand Up @@ -751,7 +796,7 @@ mod tests {
)
.expect("indoor cbrs");

assert_eq!(expected, outdoor_wifi.total_coverage_points);
assert_eq!(expected, outdoor_wifi.coverage_points_v1());
}

#[rstest]
Expand All @@ -778,7 +823,7 @@ mod tests {
)
.expect("indoor wifi");

assert_eq!(expected, indoor_wifi.total_coverage_points);
assert_eq!(expected, indoor_wifi.coverage_points_v1());
}

fn hex_location() -> hextree::Cell {
Expand Down
Loading

0 comments on commit 5606471

Please sign in to comment.