-
Notifications
You must be signed in to change notification settings - Fork 219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: estimated hashrate calculation is incorrect #3996
Merged
aviator-app
merged 16 commits into
tari-project:development
from
mrnaveira:fix-estimated-hashrate
Apr 4, 2022
+294
−28
Merged
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
212f782
calculate sha3/monero hashrates with moving avgs
mrnaveira 5c96aee
refactor hash rate into a separated file
mrnaveira 72a88eb
fix explorer rpc integration test
mrnaveira 23a0a9b
Merge branch 'development' of https://github.com/mrnaveira/tari into …
mrnaveira 4ce61ae
create custom moving average calculation
mrnaveira e0e1e71
refactor difficulties helper in integration tests
mrnaveira 874f24e
show hash rates in text explorer
mrnaveira 8bcebf7
improve hash rate overflow test
mrnaveira c32ff15
create a field for the total hash rate
mrnaveira 95aab82
fix javascript lint errors
mrnaveira 1ae714e
fix javascript prettier errors
mrnaveira a9f9811
fix cargo clippy warnings
mrnaveira 17e2b77
Merge branch 'development' into fix-estimated-hashrate
stringhandler fd3d46c
implement suggested pr changes
mrnaveira 6bf5874
Merge branch 'fix-estimated-hashrate' of https://github.com/mrnaveira…
mrnaveira 6bbfe9f
Merge branch 'development' into fix-estimated-hashrate
aviator-app[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice |
||
.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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need a comment here for why these values are different