From 10c451f3b77b697ff1ff4a86dc3c35178fa7dc44 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 13 May 2024 16:51:13 +0400 Subject: [PATCH 1/2] Added PoS grinding attack fuse --- src/currency_core/blockchain_storage.cpp | 99 +++++++++++++++++------- src/currency_core/blockchain_storage.h | 8 +- src/currency_core/core_runtime_config.h | 2 + src/currency_core/currency_config.h | 2 + src/wallet/wallet2.cpp | 5 +- tests/core_tests/chaingen_main.cpp | 3 + tests/core_tests/chaingen_tests_list.h | 1 + tests/core_tests/pos_fuse_test.cpp | 98 +++++++++++++++++++++++ tests/core_tests/pos_fuse_test.h | 14 ++++ 9 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 tests/core_tests/pos_fuse_test.cpp create mode 100644 tests/core_tests/pos_fuse_test.h diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index ba9e2b325..a16302f14 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -67,6 +67,8 @@ using namespace currency; #define BLOCKCHAIN_STORAGE_OPTIONS_ID_LAST_WORKED_VERSION 2 #define BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION 3 //DON'T CHANGE THIS, if you need to resync db change BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION #define BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MINOR_COMPATIBILITY_VERSION 4 //mismatch here means some reinitializations +#define BLOCKCHAIN_STORAGE_OPTIONS_ID_MAJOR_FAILURE 5 //if not blocks should ever be added with this condition + #define TARGETDATA_CACHE_SIZE DIFFICULTY_WINDOW + 10 @@ -96,6 +98,7 @@ blockchain_storage::blockchain_storage(tx_memory_pool& tx_pool) :m_db(nullptr, m m_db_last_worked_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_LAST_WORKED_VERSION, m_db_solo_options), m_db_storage_major_compatibility_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION, m_db_solo_options), m_db_storage_minor_compatibility_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MINOR_COMPATIBILITY_VERSION, m_db_solo_options), + m_db_major_failure(BLOCKCHAIN_STORAGE_OPTIONS_ID_MAJOR_FAILURE, m_db_solo_options), m_db_per_block_gindex_incs(m_db), m_tx_pool(tx_pool), m_is_in_checkpoint_zone(false), @@ -507,7 +510,8 @@ bool blockchain_storage::init(const std::string& config_folder, const boost::pro << " last block: " << m_db_blocks.size() - 1 << ", " << misc_utils::get_time_interval_string(timestamp_diff) << " ago" << ENDL << " current pos difficulty: " << get_next_diff_conditional(true) << ENDL << " current pow difficulty: " << get_next_diff_conditional(false) << ENDL - << " total transactions: " << m_db_transactions.size(), + << " total transactions: " << m_db_transactions.size() << ENDL + << " major failure: " << (m_db_major_failure ? "true" : "false"), LOG_LEVEL_0); return true; @@ -1181,14 +1185,9 @@ bool blockchain_storage::switch_to_alternative_blockchain(alt_chain_type& alt_ch return true; } //------------------------------------------------------------------ -wide_difficulty_type blockchain_storage::get_next_diff_conditional(bool pos) const +void blockchain_storage::collect_timestamps_and_c_difficulties_main(std::vector& timestamps, std::vector& commulative_difficulties, bool pos) const { CRITICAL_REGION_LOCAL(m_read_lock); - std::vector timestamps; - std::vector commulative_difficulties; - if (!m_db_blocks.size()) - return DIFFICULTY_POW_STARTER; - //skip genesis timestamp TIME_MEASURE_START_PD(target_calculating_enum_blocks); CRITICAL_REGION_BEGIN(m_targetdata_cache_lock); std::list>& targetdata_cache = pos ? m_pos_targetdata_cache : m_pow_targetdata_cache; @@ -1203,11 +1202,14 @@ wide_difficulty_type blockchain_storage::get_next_diff_conditional(bool pos) con ++count; } CRITICAL_REGION_END(); - - wide_difficulty_type& dif = pos ? m_cached_next_pos_difficulty : m_cached_next_pow_difficulty; TIME_MEASURE_FINISH_PD(target_calculating_enum_blocks); +} +//------------------------------------------------------------------ +wide_difficulty_type blockchain_storage::calc_diff_at_h_from_timestamps(std::vector& timestamps, std::vector& commulative_difficulties, uint64_t h, bool pos) const +{ + wide_difficulty_type dif; TIME_MEASURE_START_PD(target_calculating_calc); - if (m_core_runtime_config.is_hardfork_active_for_height(1, m_db_blocks.size())) + if (m_core_runtime_config.is_hardfork_active_for_height(1, h)) { dif = next_difficulty_2(timestamps, commulative_difficulties, pos ? global_difficulty_pos_target : global_difficulty_pow_target, pos ? global_difficulty_pos_starter : global_difficulty_pow_starter); } @@ -1215,22 +1217,36 @@ wide_difficulty_type blockchain_storage::get_next_diff_conditional(bool pos) con { dif = next_difficulty_1(timestamps, commulative_difficulties, pos ? global_difficulty_pos_target : global_difficulty_pow_target, pos ? global_difficulty_pos_starter : global_difficulty_pow_starter); } - - TIME_MEASURE_FINISH_PD(target_calculating_calc); return dif; } //------------------------------------------------------------------ -wide_difficulty_type blockchain_storage::get_next_diff_conditional2(bool pos, const alt_chain_type& alt_chain, uint64_t split_height, const alt_block_extended_info& abei) const +wide_difficulty_type blockchain_storage::get_next_diff_conditional(bool pos) const { - CRITICAL_REGION_LOCAL(m_read_lock); + { + //skip genesis timestamp + CRITICAL_REGION_LOCAL(m_read_lock); + if (!m_db_blocks.size()) + return DIFFICULTY_POW_STARTER; + } + std::vector timestamps; std::vector commulative_difficulties; + collect_timestamps_and_c_difficulties_main(timestamps, commulative_difficulties, pos); + + + wide_difficulty_type& dif = pos ? m_cached_next_pos_difficulty : m_cached_next_pow_difficulty; + dif = calc_diff_at_h_from_timestamps(timestamps, commulative_difficulties, m_db_blocks.size(), pos); + + return dif; +} +//------------------------------------------------------------------ +void blockchain_storage::collect_timestamps_and_c_difficulties_alt(std::vector& timestamps, std::vector& commulative_difficulties, bool pos, const alt_chain_type& alt_chain, uint64_t split_height) const +{ + CRITICAL_REGION_LOCAL(m_read_lock); size_t count = 0; - if (!m_db_blocks.size()) - return DIFFICULTY_POW_STARTER; - auto cb = [&](const block_extended_info& bei, bool is_main){ + auto cb = [&](const block_extended_info& bei, bool is_main) { if (!bei.height) return false; bool is_pos_bl = is_pos_block(bei.bl); @@ -1242,15 +1258,22 @@ wide_difficulty_type blockchain_storage::get_next_diff_conditional2(bool pos, co if (count >= DIFFICULTY_WINDOW) return false; return true; - }; + }; enum_blockchain(cb, alt_chain, split_height); +} +//------------------------------------------------------------------ +wide_difficulty_type blockchain_storage::get_next_diff_conditional_alt(bool pos, const alt_chain_type& alt_chain, uint64_t split_height, const alt_block_extended_info& abei) const +{ + { + CRITICAL_REGION_LOCAL(m_read_lock); + if (!m_db_blocks.size()) + return DIFFICULTY_POW_STARTER; + } + std::vector timestamps; + std::vector commulative_difficulties; + collect_timestamps_and_c_difficulties_alt(timestamps, commulative_difficulties, pos, alt_chain, split_height); - wide_difficulty_type diff = 0; - if(m_core_runtime_config.is_hardfork_active_for_height(1, abei.height)) - diff = next_difficulty_2(timestamps, commulative_difficulties, pos ? global_difficulty_pos_target : global_difficulty_pow_target, pos ? global_difficulty_pos_starter : global_difficulty_pow_starter); - else - diff = next_difficulty_1(timestamps, commulative_difficulties, pos ? global_difficulty_pos_target : global_difficulty_pow_target, pos ? global_difficulty_pos_starter : global_difficulty_pow_starter); - return diff; + return calc_diff_at_h_from_timestamps(timestamps, commulative_difficulties, abei.height, pos); } //------------------------------------------------------------------ wide_difficulty_type blockchain_storage::get_cached_next_difficulty(bool pos) const @@ -1865,7 +1888,7 @@ bool blockchain_storage::handle_alternative_block(const block& b, const crypto:: } // PoW / PoS validation (heavy checks) - wide_difficulty_type current_diff = get_next_diff_conditional2(pos_block, alt_chain, connection_height, abei); + wide_difficulty_type current_diff = get_next_diff_conditional_alt(pos_block, alt_chain, connection_height, abei); CHECK_AND_ASSERT_MES_CUSTOM(current_diff, false, bvc.m_verification_failed = true, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); crypto::hash proof_of_work = null_hash; @@ -5751,6 +5774,12 @@ void blockchain_storage::get_pos_mining_estimate(uint64_t amount_coins, //------------------------------------------------------------------ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transaction& tx, const crypto::hash& tx_id) const { + if (m_db_major_failure) + { + LOG_ERROR("MAJOR FAILURE: POS DIFFICULTY IS GOT TO HIGH! Contact the team immediately if you see this error in logs and watch them having panic attack."); + return false; + } + uint64_t block_height = m_db_blocks.size(); return validate_tx_for_hardfork_specific_terms(tx, tx_id, block_height); } @@ -6780,6 +6809,13 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt << range_proofs_agregated.size() << ")" << "))"); + if (is_pos_bl && current_diffic > m_core_runtime_config.max_pos_difficulty) + { + m_db_major_failure = true; //burn safety fuse + LOG_ERROR("MAJOR FAILURE: POS DIFFICULTY IS GOT TO HIGH! Contact the team immediately if you see this error in logs and watch them having panic attack." + << ENDL << "Block id:" << id); + } + { static epee::math_helper::average blocks_processing_time_avg_pos, blocks_processing_time_avg_pow; @@ -6956,6 +6992,15 @@ bool blockchain_storage::add_new_block(const block& bl, block_verification_conte { try { + + if (m_db_major_failure) + { + LOG_PRINT_RED_L0("Block processing is stoped due to MAJOR FAILURE fuse burned"); + bvc.m_added_to_main_chain = false; + bvc.m_verification_failed = true; + return false; + } + m_db.begin_transaction(); //block bl = bl_; @@ -6979,10 +7024,6 @@ bool blockchain_storage::add_new_block(const block& bl, block_verification_conte m_db.commit_transaction(); return false; } - - - //check that block refers to chain tail - if (!(bl.prev_id == get_top_block_id())) { diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 97f712e10..489f46359 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -266,8 +266,12 @@ namespace currency crypto::hash get_top_block_id(uint64_t& height) const; bool get_top_block(block& b) const; wide_difficulty_type get_next_diff_conditional(bool pos) const; - wide_difficulty_type get_next_diff_conditional2(bool pos, const alt_chain_type& alt_chain, uint64_t split_height, const alt_block_extended_info& abei) const; + wide_difficulty_type get_next_diff_conditional_alt(bool pos, const alt_chain_type& alt_chain, uint64_t split_height, const alt_block_extended_info& abei) const; wide_difficulty_type get_cached_next_difficulty(bool pos) const; + wide_difficulty_type calc_diff_at_h_from_timestamps(std::vector& timestamps, std::vector& commulative_difficulties, uint64_t h, bool pos) const; + void collect_timestamps_and_c_difficulties_main(std::vector& timestamps, std::vector& commulative_difficulties, bool pos) const; + void collect_timestamps_and_c_difficulties_alt(std::vector& timestamps, std::vector& commulative_difficulties, bool pos, const alt_chain_type& alt_chain, uint64_t split_height) const; + bool create_block_template(const account_public_address& miner_address, const blobdata& ex_nonce, block& b, wide_difficulty_type& di, uint64_t& height) const; @@ -551,6 +555,8 @@ namespace currency tools::db::solo_db_value m_db_last_worked_version; tools::db::solo_db_value m_db_storage_major_compatibility_version; tools::db::solo_db_value m_db_storage_minor_compatibility_version; + tools::db::solo_db_value m_db_major_failure; //safety fuse + outputs_container m_db_outputs; multisig_outs_container m_db_multisig_outs; aliases_container m_db_aliases; diff --git a/src/currency_core/core_runtime_config.h b/src/currency_core/core_runtime_config.h index f970e1445..c1ba86af4 100644 --- a/src/currency_core/core_runtime_config.h +++ b/src/currency_core/core_runtime_config.h @@ -106,6 +106,7 @@ namespace currency crypto::public_key alias_validation_pubkey; core_time_func_t get_core_time; uint64_t hf4_minimum_mixins; + wide_difficulty_type max_pos_difficulty; hard_forks_descriptor hard_forks; @@ -129,6 +130,7 @@ namespace currency pc.tx_default_fee = TX_DEFAULT_FEE; pc.max_alt_blocks = CURRENCY_ALT_BLOCK_MAX_COUNT; pc.hf4_minimum_mixins = CURRENCY_HF4_MANDATORY_DECOY_SET_SIZE; + pc.max_pos_difficulty = wide_difficulty_type(POS_MAX_DIFFICULTY_ALLOWED); // TODO: refactor the following pc.hard_forks.set_hardfork_height(1, ZANO_HARDFORK_01_AFTER_HEIGHT); diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index e6a21e0b0..c73845101 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -157,6 +157,8 @@ #define POS_MODFIFIER_INTERVAL 10 #define POS_WALLET_MINING_SCAN_INTERVAL POS_SCAN_STEP //seconds #define POS_MINIMUM_COINSTAKE_AGE 10 // blocks count +#define POS_MAX_DIFFICULTY_ALLOWED "25000000000000000000000" // maximum expected PoS difficuty (need to change it probaly in 20 years) + #ifndef TESTNET # define BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION 57000 diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3b1144aa2..d5720fe31 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4887,15 +4887,16 @@ bool wallet2::try_mint_pos(const currency::account_public_address& miner_address return true; }, m_core_runtime_config); + bool res = true; if (ctx.status == API_RETURN_CODE_OK) { - build_minted_block(ctx, miner_address); + res = build_minted_block(ctx, miner_address); } TIME_MEASURE_FINISH_MS(mining_duration_ms); WLT_LOG_L0("PoS mining: " << ctx.iterations_processed << " iterations finished (" << std::fixed << std::setprecision(2) << (mining_duration_ms / 1000.0f) << "s), status: " << ctx.status << ", " << ctx.total_items_checked << " entries with total amount: " << print_money_brief(ctx.total_amount_checked)); - return true; + return res; } //------------------------------------------------------------------ void wallet2::do_pos_mining_prepare_entry(mining_context& context, size_t transfer_index) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 71bb8fe10..aad760ea7 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1274,6 +1274,9 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(zarcanum_block_with_txs); GENERATE_AND_PLAY(asset_depoyment_and_few_zc_utxos); GENERATE_AND_PLAY_HF(assets_and_pos_mining, "4-*"); + + GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); + GENERATE_AND_PLAY_HF(attachment_isolation_test, "4-*"); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index c6a952f67..abac7b002 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -45,3 +45,4 @@ #include "multiassets_test.h" #include "ionic_swap_tests.h" #include "attachment_isolation_encryption_test.h" +#include "pos_fuse_test.h" \ No newline at end of file diff --git a/tests/core_tests/pos_fuse_test.cpp b/tests/core_tests/pos_fuse_test.cpp new file mode 100644 index 000000000..109e0b026 --- /dev/null +++ b/tests/core_tests/pos_fuse_test.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2014-2022 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "chaingen.h" +#include "pos_fuse_test.h" +#include "wallet_test_core_proxy.h" + +#include "random_helper.h" +#include "wallet/wallet_debug_events_definitions.h" +using namespace currency; + + +using namespace currency; +pos_fuse_test::pos_fuse_test() +{ + REGISTER_CALLBACK_METHOD(pos_fuse_test, c1); + REGISTER_CALLBACK_METHOD(pos_fuse_test, configure_core); +} + + bool pos_fuse_test::configure_core(currency::core& c, size_t ev_index, const std::vector& events) +{ + wallet_test::configure_core(c, ev_index, events); + currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config(); + pc.max_pos_difficulty = wide_difficulty_type(1); + //currency::core_runtime_config pc2; + //pc2 = pc; + c.get_blockchain_storage().set_core_runtime_config(pc); + + + currency::core_runtime_config pc2 = c.get_blockchain_storage().get_core_runtime_config(); + LOG_PRINT_L1("Difficulty: " << pc2.max_pos_difficulty); + + return true; +} + +bool pos_fuse_test::generate(std::vector& events) const +{ + uint64_t ts = test_core_time::get_time(); + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts); + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); + DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks + + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 10); + + DO_CALLBACK(events, "c1"); + return true; +} + +bool pos_fuse_test::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + miner_wlt->refresh(); + + + + while (true) + { + miner_wlt->refresh(); + wide_difficulty_type pos_diff = c.get_blockchain_storage().get_next_diff_conditional(true); + + r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + + bool r = miner_wlt->try_mint_pos(); + CHECK_AND_ASSERT_MES(r, false, "Failed ot mint pos block"); + + currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config(); + LOG_PRINT_MAGENTA("POS Difficulty: " << pos_diff << ", max allowed diff: " << pc.max_pos_difficulty, LOG_LEVEL_0); + if (pos_diff > pc.max_pos_difficulty) + { + break; + } + } + + //check that PoW blocks not going + r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); + CHECK_AND_ASSERT_MES(!r, false, "PoW block unexpectedly generated"); + + //check that PoS blocks not going + r = miner_wlt->try_mint_pos(); + CHECK_AND_ASSERT_MES(!r, false, "PoS block unexpectedly mined"); + + try + { + miner_wlt->transfer(1000000, m_accounts[ALICE_ACC_IDX].get_public_address()); + CHECK_AND_ASSERT_MES(false, false, "Transaction unexpectedly sent"); + } + catch (...) + { + LOG_PRINT_L0("Expected exception catched"); + } + + return true; +} diff --git a/tests/core_tests/pos_fuse_test.h b/tests/core_tests/pos_fuse_test.h new file mode 100644 index 000000000..fb9d8c568 --- /dev/null +++ b/tests/core_tests/pos_fuse_test.h @@ -0,0 +1,14 @@ +// Copyright (c) 2014-2024 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once +#include "chaingen.h" +#include "wallet_tests_basic.h" + +struct pos_fuse_test : public wallet_test +{ + pos_fuse_test(); + virtual bool configure_core(currency::core& c, size_t ev_index, const std::vector& events); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; \ No newline at end of file From df6fa3f7c5d1eaf6ae0e2f91a3bb3d55b25b4a29 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Mon, 13 May 2024 16:45:05 +0300 Subject: [PATCH 2/2] === build number: 313 -> 314 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 88600b4e3..d790e0492 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 313 +#define PROJECT_VERSION_BUILD_NO 314 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]"