-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: estimated hashrate calculation is incorrect (#3996)
Description --- * Created new fields in the base_node grpc api for sha3 and monero hash rates, using an independent moving average for each one * Kept the `estimated_hash_rate` field but now it corresponds to the sum of the sha3 and monero hash rates, so existing clients (i.e. web explorer) will not break * Modified the text explorer to show the estimated hash rates Motivation and Context --- The current hash rate calculation is incorrect, we want to use a separated moving average for each PoW algo independently How Has This Been Tested? --- Unit testing for the moving average hash rate calculation
- Loading branch information
Showing
8 changed files
with
294 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
// Copyright 2022. The Tari Project | ||
// | ||
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the | ||
// following conditions are met: | ||
// | ||
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following | ||
// disclaimer. | ||
// | ||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the | ||
// following disclaimer in the documentation and/or other materials provided with the distribution. | ||
// | ||
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote | ||
// products derived from this software without specific prior written permission. | ||
// | ||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | ||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | ||
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
|
||
use std::collections::VecDeque; | ||
|
||
use tari_core::{ | ||
consensus::ConsensusManager, | ||
proof_of_work::{Difficulty, PowAlgorithm}, | ||
}; | ||
|
||
/// The number of past blocks to be used on moving averages for (smooth) estimated hashrate | ||
/// We consider a 60 minute time window reasonable, that means 12 SHA3 blocks and 18 Monero blocks | ||
const SHA3_HASH_RATE_MOVING_AVERAGE_WINDOW: usize = 12; | ||
const MONERO_HASH_RATE_MOVING_AVERAGE_WINDOW: usize = 18; | ||
|
||
/// Calculates a linear weighted moving average for hash rate calculations | ||
pub struct HashRateMovingAverage { | ||
pow_algo: PowAlgorithm, | ||
consensus_manager: ConsensusManager, | ||
window_size: usize, | ||
hash_rates: VecDeque<u64>, | ||
average: u64, | ||
} | ||
|
||
impl HashRateMovingAverage { | ||
pub fn new(pow_algo: PowAlgorithm, consensus_manager: ConsensusManager) -> Self { | ||
let window_size = match pow_algo { | ||
PowAlgorithm::Monero => MONERO_HASH_RATE_MOVING_AVERAGE_WINDOW, | ||
PowAlgorithm::Sha3 => SHA3_HASH_RATE_MOVING_AVERAGE_WINDOW, | ||
}; | ||
let hash_rates = VecDeque::with_capacity(window_size); | ||
|
||
Self { | ||
pow_algo, | ||
consensus_manager, | ||
window_size, | ||
hash_rates, | ||
average: 0, | ||
} | ||
} | ||
|
||
/// Adds a new hash rate entry in the moving average and recalculates the average | ||
pub fn add(&mut self, height: u64, difficulty: Difficulty) { | ||
// target block time for the current block is provided by the consensus rules | ||
let target_time = self | ||
.consensus_manager | ||
.consensus_constants(height) | ||
.get_diff_target_block_interval(self.pow_algo); | ||
|
||
// remove old entries if we are at max block window | ||
if self.is_full() { | ||
self.hash_rates.pop_back(); | ||
} | ||
|
||
// add the new hash rate to the list | ||
let current_hash_rate = difficulty.as_u64() / target_time; | ||
self.hash_rates.push_front(current_hash_rate); | ||
|
||
// after adding the hash rate we need to recalculate the average | ||
self.average = self.calculate_average(); | ||
} | ||
|
||
fn is_full(&self) -> bool { | ||
self.hash_rates.len() >= self.window_size | ||
} | ||
|
||
fn calculate_average(&self) -> u64 { | ||
// this check is not strictly necessary as this is only called after adding an item | ||
// but let's be on the safe side for future changes | ||
if self.hash_rates.is_empty() { | ||
return 0; | ||
} | ||
|
||
let sum: u64 = self.hash_rates.iter().sum(); | ||
let count = self.hash_rates.len() as u64; | ||
sum / count | ||
} | ||
|
||
pub fn average(&self) -> u64 { | ||
self.average | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use tari_core::{ | ||
consensus::{ConsensusConstants, ConsensusManagerBuilder}, | ||
proof_of_work::{Difficulty, PowAlgorithm}, | ||
}; | ||
use tari_p2p::Network; | ||
|
||
use super::HashRateMovingAverage; | ||
|
||
#[test] | ||
fn window_is_empty() { | ||
let hash_rate_ma = create_hash_rate_ma(PowAlgorithm::Sha3); | ||
assert!(!hash_rate_ma.is_full()); | ||
assert_eq!(hash_rate_ma.calculate_average(), 0); | ||
assert_eq!(hash_rate_ma.average(), 0); | ||
} | ||
|
||
#[test] | ||
fn window_is_full() { | ||
let mut hash_rate_ma = create_hash_rate_ma(PowAlgorithm::Sha3); | ||
let window_size = hash_rate_ma.window_size; | ||
|
||
// we check that the window is not full when we insert less items than the window size | ||
for _ in 0..window_size - 1 { | ||
hash_rate_ma.add(0, Difficulty::from(0)); | ||
assert!(!hash_rate_ma.is_full()); | ||
} | ||
|
||
// from this point onwards, the window should be always full | ||
for _ in 0..10 { | ||
hash_rate_ma.add(0, Difficulty::from(0)); | ||
assert!(hash_rate_ma.is_full()); | ||
} | ||
} | ||
|
||
// Checks that the moving average hash rate at every block is correct | ||
// We use larger sample data than the SHA window size (12 periods) to check bounds | ||
// We assumed a constant target block time of 300 secs (the SHA3 target time for Dibbler) | ||
// These expected hash rate values where calculated in a spreadsheet | ||
#[test] | ||
fn correct_moving_average_calculation() { | ||
let mut hash_rate_ma = create_hash_rate_ma(PowAlgorithm::Sha3); | ||
|
||
assert_hash_rate(&mut hash_rate_ma, 0, 100_000, 333); | ||
assert_hash_rate(&mut hash_rate_ma, 1, 120_100, 366); | ||
assert_hash_rate(&mut hash_rate_ma, 2, 110_090, 366); | ||
assert_hash_rate(&mut hash_rate_ma, 3, 121_090, 375); | ||
assert_hash_rate(&mut hash_rate_ma, 4, 150_000, 400); | ||
assert_hash_rate(&mut hash_rate_ma, 5, 155_000, 419); | ||
assert_hash_rate(&mut hash_rate_ma, 6, 159_999, 435); | ||
assert_hash_rate(&mut hash_rate_ma, 7, 160_010, 448); | ||
assert_hash_rate(&mut hash_rate_ma, 8, 159_990, 457); | ||
assert_hash_rate(&mut hash_rate_ma, 9, 140_000, 458); | ||
assert_hash_rate(&mut hash_rate_ma, 10, 137_230, 458); | ||
assert_hash_rate(&mut hash_rate_ma, 11, 130_000, 456); | ||
assert_hash_rate(&mut hash_rate_ma, 12, 120_000, 461); | ||
assert_hash_rate(&mut hash_rate_ma, 13, 140_000, 467); | ||
} | ||
|
||
// Our moving average windows are very small (12 and 15 depending on PoW algorithm) | ||
// So we will never get an overflow when we do the sums for the average calculation (we divide by target time) | ||
// Anyways, just in case we go with huge windows in the future, this test should fail with a panic due to overflow | ||
#[test] | ||
fn should_not_overflow() { | ||
let mut sha3_hash_rate_ma = create_hash_rate_ma(PowAlgorithm::Sha3); | ||
let mut monero_hash_rate_ma = create_hash_rate_ma(PowAlgorithm::Monero); | ||
try_to_overflow(&mut sha3_hash_rate_ma); | ||
try_to_overflow(&mut monero_hash_rate_ma); | ||
} | ||
|
||
fn try_to_overflow(hash_rate_ma: &mut HashRateMovingAverage) { | ||
let window_size = hash_rate_ma.window_size; | ||
|
||
for _ in 0..window_size { | ||
hash_rate_ma.add(0, Difficulty::from(u64::MAX)); | ||
} | ||
} | ||
|
||
fn create_hash_rate_ma(pow_algo: PowAlgorithm) -> HashRateMovingAverage { | ||
let consensus_manager = ConsensusManagerBuilder::new(Network::Dibbler) | ||
.add_consensus_constants(ConsensusConstants::dibbler()[0].clone()) | ||
.build(); | ||
HashRateMovingAverage::new(pow_algo, consensus_manager) | ||
} | ||
|
||
fn assert_hash_rate( | ||
moving_average: &mut HashRateMovingAverage, | ||
height: u64, | ||
difficulty: u64, | ||
expected_hash_rate: u64, | ||
) { | ||
moving_average.add(height, Difficulty::from(difficulty)); | ||
assert_eq!(moving_average.average(), expected_hash_rate); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,5 @@ | |
|
||
pub mod base_node_grpc_server; | ||
pub mod blocks; | ||
pub mod hash_rate; | ||
pub mod helpers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.