From 7bd66b01bfb62ecaf8ff02b20d14db3a77322abc Mon Sep 17 00:00:00 2001 From: stoffu Date: Fri, 15 May 2020 10:56:23 +0900 Subject: daemon: guard against rare 'difficulty drift' bug with checkpoints and recalculation On startup, it checks against the difficulty checkpoints, and if any mismatch is found, recalculates all the blocks with wrong difficulties. Additionally, once a week it recalculates difficulties of blocks after the last difficulty checkpoint. --- src/cryptonote_core/blockchain.cpp | 115 ++++++++++++++++++++++++++++++++ src/cryptonote_core/blockchain.h | 16 +++++ src/cryptonote_core/cryptonote_core.cpp | 7 ++ src/cryptonote_core/cryptonote_core.h | 8 +++ 4 files changed, 146 insertions(+) (limited to 'src/cryptonote_core') diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index fb2c71a3a..cad4620d2 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "include_base_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -439,6 +440,15 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline m_long_term_block_weights_cache_rolling_median = epee::misc_utils::rolling_median_t(m_long_term_block_weights_window); } + bool difficulty_ok; + uint64_t difficulty_recalc_height; + std::tie(difficulty_ok, difficulty_recalc_height) = check_difficulty_checkpoints(); + if (!difficulty_ok) + { + MERROR("Difficulty drift detected!"); + recalculate_difficulties(difficulty_recalc_height); + } + { db_txn_guard txn_guard(m_db, m_db->is_read_only()); if (!update_next_cumulative_weight_limit()) @@ -888,6 +898,111 @@ difficulty_type Blockchain::get_difficulty_for_next_block() return diff; } //------------------------------------------------------------------ +std::pair Blockchain::check_difficulty_checkpoints() const +{ + uint64_t res = 0; + for (const std::pair& i : m_checkpoints.get_difficulty_points()) + { + if (i.first >= m_db->height()) + break; + if (m_db->get_block_cumulative_difficulty(i.first) != i.second) + return {false, res}; + res = i.first; + } + return {true, res}; +} +//------------------------------------------------------------------ +size_t Blockchain::recalculate_difficulties(boost::optional start_height_opt) +{ + if (m_fixed_difficulty) + { + return 0; + } + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + const uint64_t start_height = start_height_opt ? *start_height_opt : check_difficulty_checkpoints().second; + const uint64_t top_height = m_db->height() - 1; + MGINFO("Recalculating difficulties from height " << start_height << " to height " << top_height); + + std::vector timestamps; + std::vector difficulties; + timestamps.reserve(DIFFICULTY_BLOCKS_COUNT + 1); + difficulties.reserve(DIFFICULTY_BLOCKS_COUNT + 1); + if (start_height > 1) + { + for (uint64_t i = 0; i < DIFFICULTY_BLOCKS_COUNT; ++i) + { + uint64_t height = start_height - 1 - i; + if (height == 0) + break; + timestamps.insert(timestamps.begin(), m_db->get_block_timestamp(height)); + difficulties.insert(difficulties.begin(), m_db->get_block_cumulative_difficulty(height)); + } + } + difficulty_type last_cum_diff = start_height <= 1 ? start_height : difficulties.back(); + uint64_t drift_start_height = 0; + std::vector new_cumulative_difficulties; + for (uint64_t height = start_height; height <= top_height; ++height) + { + size_t target = get_ideal_hard_fork_version(height) < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + difficulty_type recalculated_diff = next_difficulty(timestamps, difficulties, target); + + boost::multiprecision::uint256_t recalculated_cum_diff_256 = boost::multiprecision::uint256_t(recalculated_diff) + last_cum_diff; + CHECK_AND_ASSERT_THROW_MES(recalculated_cum_diff_256 <= std::numeric_limits::max(), "Difficulty overflow!"); + difficulty_type recalculated_cum_diff = recalculated_cum_diff_256.convert_to(); + + if (drift_start_height == 0) + { + difficulty_type existing_cum_diff = m_db->get_block_cumulative_difficulty(height); + if (recalculated_cum_diff != existing_cum_diff) + { + drift_start_height = height; + new_cumulative_difficulties.reserve(top_height + 1 - height); + LOG_ERROR("Difficulty drift found at height:" << height << ", hash:" << m_db->get_block_hash_from_height(height) << ", existing:" << existing_cum_diff << ", recalculated:" << recalculated_cum_diff); + } + } + if (drift_start_height > 0) + { + new_cumulative_difficulties.push_back(recalculated_cum_diff); + if (height % 100000 == 0) + LOG_ERROR(boost::format("%llu / %llu (%.1f%%)") % height % top_height % (100 * (height - drift_start_height) / float(top_height - drift_start_height))); + } + + if (height > 0) + { + timestamps.push_back(m_db->get_block_timestamp(height)); + difficulties.push_back(recalculated_cum_diff); + } + if (timestamps.size() > DIFFICULTY_BLOCKS_COUNT) + { + CHECK_AND_ASSERT_THROW_MES(timestamps.size() == DIFFICULTY_BLOCKS_COUNT + 1, "Wrong timestamps size: " << timestamps.size()); + timestamps.erase(timestamps.begin()); + difficulties.erase(difficulties.begin()); + } + last_cum_diff = recalculated_cum_diff; + } + + if (drift_start_height > 0) + { + LOG_ERROR("Writing to the DB..."); + try + { + m_db->correct_block_cumulative_difficulties(drift_start_height, new_cumulative_difficulties); + } + catch (const std::exception& e) + { + LOG_ERROR("Error correcting cumulative difficulties from height " << drift_start_height << ", what = " << e.what()); + } + LOG_ERROR("Corrected difficulties for " << new_cumulative_difficulties.size() << " blocks"); + // clear cache + m_difficulty_for_next_block_top_hash = crypto::null_hash; + m_timestamps_and_difficulties_height = 0; + } + + return new_cumulative_difficulties.size(); +} +//------------------------------------------------------------------ std::vector Blockchain::get_last_block_timestamps(unsigned int blocks) const { uint64_t height = m_db->height(); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 3a89cc5df..4dc2cea31 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -309,6 +309,22 @@ namespace cryptonote */ difficulty_type get_difficulty_for_next_block(); + /** + * @brief check currently stored difficulties against difficulty checkpoints + * + * @return {flag, height} flag: true if all difficulty checkpoints pass, height: the last checkpoint height before the difficulty drift bug starts + */ + std::pair check_difficulty_checkpoints() const; + + /** + * @brief recalculate difficulties for blocks after the last difficulty checkpoints to circumvent the annoying 'difficulty drift' bug + * + * @param start_height: if omitted, starts recalculation from the last difficulty checkpoint + * + * @return number of blocks whose difficulties got corrected + */ + size_t recalculate_difficulties(boost::optional start_height = boost::none); + /** * @brief adds a block to the blockchain * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 0f8bde3e9..5e4a1accd 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1662,6 +1662,7 @@ namespace cryptonote m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this)); m_blockchain_pruning_interval.do_call(boost::bind(&core::update_blockchain_pruning, this)); + m_diff_recalc_interval.do_call(boost::bind(&core::recalculate_difficulties, this)); m_miner.on_idle(); m_mempool.on_idle(); return true; @@ -1923,6 +1924,12 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::recalculate_difficulties() + { + m_blockchain_storage.recalculate_difficulties(); + return true; + } + //----------------------------------------------------------------------------------------------- void core::flush_bad_txs_cache() { bad_semantics_txes_lock.lock(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 988f25ceb..5cbec7d62 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1040,6 +1040,13 @@ namespace cryptonote */ bool check_block_rate(); + /** + * @brief recalculate difficulties after the last difficulty checklpoint to circumvent the annoying 'difficulty drift' bug + * + * @return true + */ + bool recalculate_difficulties(); + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so @@ -1065,6 +1072,7 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate epee::math_helper::once_a_time_seconds<60*60*5, true> m_blockchain_pruning_interval; //!< interval for incremental blockchain pruning + epee::math_helper::once_a_time_seconds<60*60*24*7, false> m_diff_recalc_interval; //!< interval for recalculating difficulties std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? -- cgit v1.2.3