From 13e8d0dfe35ad388b8a9e47ed4b348bf270f6b68 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 9 Jun 2023 01:10:16 +0200 Subject: [PATCH 01/18] wallet: packing/consolidating tx that automatically aggregates small UTXO in PoS blocks was ranamed to defragmentation tx to avoid confusion + re-implemented to reflect post-HF4 changes --- src/common/error_codes.h | 1 + src/wallet/wallet2.cpp | 92 +++++++++++------------------ src/wallet/wallet2.h | 15 +++-- tests/core_tests/pos_validation.cpp | 2 +- tests/core_tests/wallet_tests.cpp | 2 +- 5 files changed, 46 insertions(+), 66 deletions(-) diff --git a/src/common/error_codes.h b/src/common/error_codes.h index 313a9aad..87b7fa8c 100644 --- a/src/common/error_codes.h +++ b/src/common/error_codes.h @@ -12,6 +12,7 @@ #define API_RETURN_CODE_INTERNAL_ERROR "INTERNAL_ERROR" #define API_RETURN_CODE_NOT_ENOUGH_MONEY "NOT_ENOUGH_MONEY" #define API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_MIXING "NOT_ENOUGH_OUTPUTS_FOR_MIXING" +#define API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION "NOT_ENOUGH_OUTPUTS_FOR_OPERATION" #define API_RETURN_CODE_INTERNAL_ERROR_QUE_FULL "INTERNAL_ERROR_QUE_FULL" #define API_RETURN_CODE_BAD_ARG "BAD_ARG" #define API_RETURN_CODE_BAD_ARG_EMPTY_DESTINATIONS "BAD_ARG_EMPTY_DESTINATIONS" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 05fe16f5..07deff48 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -51,6 +51,9 @@ using namespace currency; #define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB +#define WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX 10 // TODO @#@# change to default decoy set number +#define WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 100 // TODO: @#@# consider descreasing to mimic normal tx + #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" ENABLE_CHANNEL_BY_DEFAULT("wallet") @@ -69,7 +72,8 @@ namespace tools , m_watch_only(false) , m_last_pow_block_h(0) , m_minimum_height(WALLET_MINIMUM_HEIGHT_UNSET_CONST) - , m_pos_mint_packing_size(WALLET_DEFAULT_POS_MINT_PACKING_SIZE) + , m_min_utxo_count_for_defragmentation_tx(WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX) + , m_decoys_count_for_defragmentation_tx(WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX) , m_current_wallet_file_size(0) , m_use_deffered_global_outputs(false) #ifdef DISABLE_TOR @@ -203,9 +207,9 @@ bool wallet2::set_core_proxy(const std::shared_ptr& proxy) return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_pos_mint_packing_size(uint64_t new_size) +void wallet2::set_pos_min_utxo_count_for_defragmentation_tx(uint64_t new_size) { - m_pos_mint_packing_size = new_size; + m_min_utxo_count_for_defragmentation_tx = new_size; } //---------------------------------------------------------------------------------------------------- std::shared_ptr wallet2::get_core_proxy() @@ -3363,29 +3367,11 @@ void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) con incoming_transfers = m_transfers; } //---------------------------------------------------------------------------------------------------- -bool wallet2::generate_packing_transaction_if_needed(currency::transaction& tx, uint64_t fake_outputs_number) +bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx) { - prepare_free_transfers_cache(0); - auto it = m_found_free_amounts[currency::native_coin_asset_id].find(CURRENCY_BLOCK_REWARD); - if (it == m_found_free_amounts[currency::native_coin_asset_id].end() || it->second.size() <= m_pos_mint_packing_size) - return false; - - //let's check if we have at least WALLET_POS_MINT_PACKING_SIZE transactions which is ready to go - size_t count = 0; - for (auto it_ind = it->second.begin(); it_ind != it->second.end() && count <= m_pos_mint_packing_size; it_ind++) - { - if (is_transfer_ready_to_go(m_transfers[*it_ind], fake_outputs_number)) - ++count; - } - if (count <= m_pos_mint_packing_size) - return false; construct_tx_param ctp = get_default_construct_tx_param(); - currency::tx_destination_entry de = AUTO_VAL_INIT(de); - de.addr.push_back(m_account.get_public_address()); - de.amount = m_pos_mint_packing_size*CURRENCY_BLOCK_REWARD; - ctp.dsts.push_back(de); - ctp.perform_packing = true; - + ctp.create_utxo_defragmentation_tx = true; + TRY_ENTRY(); transfer(ctp, tx, false, nullptr); CATCH_ENTRY2(false); @@ -3682,7 +3668,7 @@ bool enum_container(iterator_t it_begin, iterator_t it_end, callback_t cb) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::is_consolidating_transaction(const wallet_public::wallet_transfer_info& wti) +bool wallet2::is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti) { if (!wti.is_income) { @@ -3705,7 +3691,7 @@ void wallet2::get_recent_transfers_history(std::vectorcall_COMMAND_RPC_GETBLOCKTEMPLATE(tmpl_req, tmpl_rsp); WLT_CHECK_AND_ASSERT_MES(tmpl_rsp.status == API_RETURN_CODE_OK, false, "Failed to create block template after kernel hash found!"); @@ -5376,33 +5362,26 @@ bool wallet2::decrypt_buffer(const std::string& buff, std::string& res_buff) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources_for_packing(uint64_t items_to_pack, size_t fake_outputs_count, std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) +bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) { - prepare_free_transfers_cache(fake_outputs_count); + //prepare_free_transfers_cache(fake_outputs_count); + //free_amounts_cache_type& free_amounts_for_native_coin = m_found_free_amounts[currency::native_coin_asset_id]; - free_amounts_cache_type& free_amounts_for_native_coin = m_found_free_amounts[currency::native_coin_asset_id]; - - auto it = free_amounts_for_native_coin.find(CURRENCY_BLOCK_REWARD); - if (it == free_amounts_for_native_coin.end() || it->second.size() < m_pos_mint_packing_size) - return false; - - for (auto set_it = it->second.begin(); set_it != it->second.end() && selected_indicies.size() <= m_pos_mint_packing_size; ) + for (size_t i = 0, size = m_transfers.size(); i < size && selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx; ++i) { - if (is_transfer_ready_to_go(m_transfers[*set_it], fake_outputs_count)) - { - found_money += it->first; - selected_indicies.push_back(*set_it); - WLT_LOG_L2("Selected index: " << *set_it << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers[*set_it])); - - it->second.erase(set_it++); - } - else - set_it++; - } - if (!it->second.size()) - free_amounts_for_native_coin.erase(it); + const auto& td = m_transfers[i]; + if (!td.is_native_coin() || td.m_amount > CURRENCY_BLOCK_REWARD) + continue; - return prepare_tx_sources(fake_outputs_count, sources, selected_indicies); + if (is_transfer_ready_to_go(td, m_decoys_count_for_defragmentation_tx)) + { + found_money += td.m_amount; + selected_indicies.push_back(i); + WLT_LOG_L2("prepare_tx_sources_for_defragmentation_tx: selected index: " << i << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers[i])); + } + } + + return prepare_tx_sources(m_decoys_count_for_defragmentation_tx, sources, selected_indicies); } //---------------------------------------------------------------------------------------------------- bool wallet2::prepare_tx_sources(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies) @@ -6448,9 +6427,10 @@ void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx //uint64_t found_money = 0; TIME_MEASURE_START_MS(prepare_tx_sources_time); - if (ctp.perform_packing) + if (ctp.create_utxo_defragmentation_tx) { - prepare_tx_sources_for_packing(WALLET_DEFAULT_POS_MINT_PACKING_SIZE, 0, ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount); + bool r = prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount); + WLT_THROW_IF_FALSE_WITH_CODE(r, "utxo defragmentation tx was not prepared (not an error)", API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION); } else if (ctp.htlc_tx_id != currency::null_hash) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6e834fba..22a686c8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -50,8 +50,6 @@ #define WALLET_DEFAULT_TX_SPENDABLE_AGE 10 #define WALLET_POS_MINT_CHECK_HEIGHT_INTERVAL 1 -#define WALLET_DEFAULT_POS_MINT_PACKING_SIZE 100 - #define WALLET_TRANSFER_DETAIL_FLAG_SPENT uint32_t(1 << 0) #define WALLET_TRANSFER_DETAIL_FLAG_BLOCKED uint32_t(1 << 1) #define WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION uint32_t(1 << 2) @@ -299,7 +297,7 @@ namespace tools currency::account_public_address crypt_address; uint8_t tx_outs_attr = 0; bool shuffle = false; - bool perform_packing = false; + bool create_utxo_defragmentation_tx = false; bool need_at_least_1_zc = false; }; @@ -574,7 +572,7 @@ namespace tools const currency::account_base& get_account() const { return m_account; } void get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs = false, bool start_from_end = true); - bool is_consolidating_transaction(const wallet_public::wallet_transfer_info& wti); + bool is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti); uint64_t get_recent_transfers_total_count(); uint64_t get_transfer_entries_count(); void get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs = false); @@ -609,7 +607,7 @@ namespace tools void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id); bool set_core_proxy(const std::shared_ptr& proxy); - void set_pos_mint_packing_size(uint64_t new_size); + void set_pos_min_utxo_count_for_defragmentation_tx(uint64_t new_size); void set_minimum_height(uint64_t h); std::shared_ptr get_core_proxy(); uint64_t balance() const; @@ -1042,7 +1040,7 @@ private: bool prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& selected_indicies); bool prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money); bool prepare_tx_sources_htlc(crypto::hash htlc_tx_id, const std::string& origin, std::vector& sources, uint64_t& found_money); - bool prepare_tx_sources_for_packing(uint64_t items_to_pack, size_t fake_outputs_count, std::vector& sources, std::vector& selected_indicies, uint64_t& found_money); + bool prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money); void prefetch_global_indicies_if_needed(const std::vector& selected_indicies); assets_selection_context get_needed_money(uint64_t fee, const std::vector& dsts); void prepare_tx_destinations(const assets_selection_context& needed_money_map, @@ -1110,7 +1108,7 @@ private: void exception_handler(); void exception_handler() const; uint64_t get_minimum_allowed_fee_for_contract(const crypto::hash& ms_id); - bool generate_packing_transaction_if_needed(currency::transaction& tx, uint64_t fake_outputs_number); + bool generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx); bool store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str = nullptr); void check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp); void push_new_block_id(const crypto::hash& id, uint64_t height); @@ -1146,7 +1144,8 @@ private: std::atomic m_last_bc_timestamp; bool m_do_rise_transfer; - uint64_t m_pos_mint_packing_size; + uint64_t m_min_utxo_count_for_defragmentation_tx; + size_t m_decoys_count_for_defragmentation_tx; transfer_container m_transfers; multisig_transfer_container m_multisig_transfers; diff --git a/tests/core_tests/pos_validation.cpp b/tests/core_tests/pos_validation.cpp index 941c7b4d..03434c15 100644 --- a/tests/core_tests/pos_validation.cpp +++ b/tests/core_tests/pos_validation.cpp @@ -1085,7 +1085,7 @@ bool pos_minting_tx_packing::c1(currency::core& c, size_t ev_index, const std::v m_alice_start_amount + CURRENCY_BLOCK_REWARD * m_pos_mint_packing_size // unlocked ), false, ""); - alice_wlt->set_pos_mint_packing_size(m_pos_mint_packing_size); + alice_wlt->set_pos_min_utxo_count_for_defragmentation_tx(m_pos_mint_packing_size); // no coinbase tx outputs should be packed r = alice_wlt->try_mint_pos(); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index a342303b..4937774a 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3402,7 +3402,7 @@ bool packing_outputs_on_pos_minting_wallet::c1(currency::core& c, size_t ev_inde miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5, false, "Incorrect numbers of blocks fetched"); - miner_wlt->set_pos_mint_packing_size(4); + miner_wlt->set_pos_min_utxo_count_for_defragmentation_tx(4); CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner_wlt", m_premine_amount + m_mined_amount, uint64_max, uint64_max, 0, 0), false, ""); miner_wlt->try_mint_pos(); From e80e4a60ccadaa8571b2e7bba6664f3a07a9ed62 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 9 Jun 2023 01:19:37 +0200 Subject: [PATCH 02/18] 1) refactored block template creation and construct_miner_tx to incorporate block reward, essential for Zarcanum PoS; 2) fixed a bug in Zarcanum PoS generation; zarcanum_block_with_txs test now passes successfully --- src/currency_core/blockchain_storage.cpp | 5 ++++- src/currency_core/blockchain_storage_basic.h | 2 ++ src/currency_core/currency_format_utils.cpp | 8 ++++---- src/currency_core/currency_format_utils.h | 3 ++- src/rpc/core_rpc_server.cpp | 2 ++ src/rpc/core_rpc_server_commands_defs.h | 4 ++++ src/wallet/wallet2.cpp | 8 ++++---- src/wallet/wallet2.h | 2 +- tests/core_tests/chaingen.cpp | 10 +++++++--- tests/core_tests/chaingen.h | 5 ++++- tests/core_tests/pos_block_builder.cpp | 5 +++-- tests/core_tests/transaction_tests.cpp | 16 +++++++++------- tests/core_tests/zarcanum_test.cpp | 2 +- 13 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 405e9895..12f7ed06 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1323,7 +1323,7 @@ bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t if (is_hardfork_active(ZANO_HARDFORK_04_ZARCANUM)) // TODO @#@# consider moving to validate_tx_for_hardfork_specific_terms { CHECK_AND_ASSERT_MES(b.miner_tx.attachment.empty(), false, "coinbase transaction has attachments; attachments are not allowed for coinbase transactions."); - CHECK_AND_ASSERT_MES(b.miner_tx.proofs.size() == 3, false, "coinbase transaction has incorrect number of proofs (" << b.miner_tx.proofs.size() << "), expected 2"); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs.size() == 3, false, "coinbase transaction has incorrect number of proofs (" << b.miner_tx.proofs.size() << "), expected 3"); CHECK_AND_ASSERT_MES(b.miner_tx.proofs[0].type() == typeid(zc_asset_surjection_proof), false, "coinbase transaction has incorrect type of proof #0 (expected: zc_asset_surjection_proof)"); CHECK_AND_ASSERT_MES(b.miner_tx.proofs[1].type() == typeid(zc_outs_range_proof), false, "coinbase transaction has incorrect type of proof #1 (expected: zc_outs_range_proof)"); CHECK_AND_ASSERT_MES(b.miner_tx.proofs[2].type() == typeid(zc_balance_proof), false, "coinbase transaction has incorrect type of proof #2 (expected: zc_balance_proof)"); @@ -1518,6 +1518,8 @@ bool blockchain_storage::create_block_template(const create_block_template_param if (!block_filled) return false; + resp.txs_fee = fee; + /* instead of complicated two-phase template construction and adjustment of cumulative size with block reward we use CURRENCY_COINBASE_BLOB_RESERVED_SIZE as penalty-free coinbase transaction reservation. @@ -1528,6 +1530,7 @@ bool blockchain_storage::create_block_template(const create_block_template_param miner_address, stakeholder_address, b.miner_tx, + resp.block_reward_without_fee, get_tx_version(height, m_core_runtime_config.hard_forks), ex_nonce, CURRENCY_MINER_TX_MAX_OUTS, diff --git a/src/currency_core/blockchain_storage_basic.h b/src/currency_core/blockchain_storage_basic.h index 70556f0f..e997b58f 100644 --- a/src/currency_core/blockchain_storage_basic.h +++ b/src/currency_core/blockchain_storage_basic.h @@ -147,6 +147,8 @@ namespace currency wide_difficulty_type diffic; uint64_t height; tx_generation_context miner_tx_tgc; // bad design, a lot of copying, consider redesign -- sowle + uint64_t block_reward_without_fee; + uint64_t txs_fee; // sum of transactions' fee if any }; typedef std::unordered_map transactions_map; diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 56ef91cd..89167bdc 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -357,24 +357,24 @@ namespace currency const account_public_address &miner_address, const account_public_address &stakeholder_address, transaction& tx, + uint64_t& block_reward_without_fee, uint64_t tx_version, const blobdata& extra_nonce /* = blobdata() */, size_t max_outs /* = CURRENCY_MINER_TX_MAX_OUTS */, bool pos /* = false */, const pos_entry& pe /* = pos_entry() */, // only pe.stake_unlock_time and pe.stake_amount are used now, TODO: consider refactoring -- sowle - tx_generation_context* ogc_ptr /* = nullptr */, + tx_generation_context* ogc_ptr /* = nullptr */, const keypair* tx_one_time_key_to_use /* = nullptr */ ) { bool r = false; - uint64_t block_reward = 0; - if (!get_block_reward(pos, median_size, current_block_size, already_generated_coins, block_reward, height)) + if (!get_block_reward(pos, median_size, current_block_size, already_generated_coins, block_reward_without_fee, height)) { LOG_ERROR("Block is too big"); return false; } - block_reward += fee; + uint64_t block_reward = block_reward_without_fee + fee; // // prepare destinations diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 0d8ebaf5..bcd15258 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -255,7 +255,8 @@ namespace currency uint64_t fee, const account_public_address &miner_address, const account_public_address &stakeholder_address, - transaction& tx, + transaction& tx, + uint64_t& block_reward_without_fee, uint64_t tx_version, const blobdata& extra_nonce = blobdata(), size_t max_outs = CURRENCY_MINER_TX_MAX_OUTS, diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 456c84a9..00646db6 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -912,6 +912,8 @@ namespace currency res.prev_hash = string_tools::pod_to_hex(resp.b.prev_id); res.miner_tx_tgc = resp.miner_tx_tgc; res.height = resp.height; + res.block_reward_without_fee = resp.block_reward_without_fee; + res.txs_fee = resp.txs_fee; //calculate epoch seed res.seed = currency::ethash_epoch_to_seed(currency::ethash_height_to_epoch(res.height)); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 4926e3c9..f36f6c1f 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -861,6 +861,8 @@ namespace currency blobdata blocktemplate_blob; std::string prev_hash; tx_generation_context miner_tx_tgc; + uint64_t block_reward_without_fee; + uint64_t txs_fee; std::string status; BEGIN_KV_SERIALIZE_MAP() @@ -870,6 +872,8 @@ namespace currency KV_SERIALIZE(blocktemplate_blob) KV_SERIALIZE(prev_hash) KV_SERIALIZE(miner_tx_tgc) + KV_SERIALIZE(block_reward_without_fee) + KV_SERIALIZE(txs_fee) KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 07deff48..39d669ed 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3898,7 +3898,7 @@ bool wallet2::is_in_hardfork_zone(uint64_t hardfork_index) const return m_core_runtime_config.is_hardfork_active_for_height(hardfork_index, get_blockchain_current_size()); } //---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, currency::block& b, const pos_entry& pe, currency::tx_generation_context& miner_tx_tgc) const +bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const { bool r = false; WLT_CHECK_AND_ASSERT_MES(pe.wallet_index < m_transfers.size(), false, "invalid pe.wallet_index: " << pe.wallet_index); @@ -4082,9 +4082,8 @@ bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, currency::bl b.miner_tx.proofs.emplace_back(std::move(range_proofs)); // balance proof - uint64_t block_reward = COIN; // TODO @#@# move it somewhere -- sowle currency::zc_balance_proof balance_proof{}; - r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, block_reward, balance_proof); + r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, full_block_reward, balance_proof); WLT_CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); b.miner_tx.proofs.emplace_back(std::move(balance_proof)); @@ -4243,7 +4242,8 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::acco set_block_datetime(current_timestamp, b); WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_0); - res = prepare_and_sign_pos_block(cxt, b, tmpl_req.pe, tmpl_rsp.miner_tx_tgc); + uint64_t full_block_reward = tmpl_rsp.block_reward_without_fee + tmpl_rsp.txs_fee; + res = prepare_and_sign_pos_block(cxt, full_block_reward, tmpl_req.pe, tmpl_rsp.miner_tx_tgc, b); WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); crypto::hash block_hash = get_block_hash(b); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 22a686c8..6046e736 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -883,7 +883,7 @@ namespace tools //next functions in public area only becausce of test_generator //TODO: Need refactoring - remove it back to private zone void set_genesis(const crypto::hash& genesis_hash); - bool prepare_and_sign_pos_block(const mining_context& cxt, currency::block& b, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc) const; + bool prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const; void process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 45b51919..03f61dee 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -299,6 +299,7 @@ bool test_generator::construct_block(currency::block& blk, blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx); size_t target_block_size = txs_size + 0; // zero means no cost for ordinary coinbase tx_generation_context miner_tx_tgc{}; + uint64_t block_reward_without_fee = 0; while (true) { r = construct_miner_tx(height, misc_utils::median(block_sizes), @@ -308,6 +309,7 @@ bool test_generator::construct_block(currency::block& blk, miner_acc.get_keys().account_address, miner_acc.get_keys().account_address, blk.miner_tx, + block_reward_without_fee, get_tx_version(height, m_hardforks), blobdata(), test_generator::get_test_gentime_settings().miner_tx_max_outs, @@ -354,7 +356,7 @@ bool test_generator::construct_block(currency::block& blk, else { //need to build pos block - r = sign_block(wallets[won_walled_index].mining_context, pe, *wallets[won_walled_index].wallet, miner_tx_tgc, blk); + r = sign_block(wallets[won_walled_index].mining_context, pe, block_reward_without_fee + total_fee, *wallets[won_walled_index].wallet, miner_tx_tgc, blk); CHECK_AND_ASSERT_MES(r, false, "Failed to find_kernel_and_sign()"); } @@ -373,11 +375,12 @@ bool test_generator::construct_block(currency::block& blk, bool test_generator::sign_block(const tools::wallet2::mining_context& mining_context, const pos_entry& pe, + uint64_t full_block_reward, const tools::wallet2& w, tx_generation_context& miner_tx_tgc, currency::block& b) { - bool r = w.prepare_and_sign_pos_block(mining_context, b, pe, miner_tx_tgc); + bool r = w.prepare_and_sign_pos_block(mining_context, full_block_reward, pe, miner_tx_tgc, b); CHECK_AND_ASSERT_MES(r, false, "prepare_and_sign_pos_block failed"); return true; } @@ -933,9 +936,10 @@ bool test_generator::construct_block(int64_t manual_timestamp_adjustment, } else { + uint64_t base_block_reward = 0; size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); // TODO: This will work, until size of constructed block is less then CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE - if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_public_address(), miner_acc.get_public_address(), blk.miner_tx, get_tx_version(height, m_hardforks), blobdata(), 1)) + if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_public_address(), miner_acc.get_public_address(), blk.miner_tx, base_block_reward, get_tx_version(height, m_hardforks), blobdata(), 1)) return false; } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 936e422a..96068a3b 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -488,6 +488,7 @@ public: bool sign_block(const tools::wallet2::mining_context& mining_context, const currency::pos_entry& pe, + uint64_t full_block_reward, const tools::wallet2& w, currency::tx_generation_context& miner_tx_tgc, currency::block& b); @@ -951,7 +952,9 @@ bool test_generator::construct_block_gentime_with_coinbase_cb(const currency::bl size_t height = get_block_height(prev_block) + 1; //size_t current_block_size = get_object_blobsize(miner_tx); - r = construct_miner_tx(height, epee::misc_utils::median(block_sizes), already_generated_coins, 0 /* current_block_size !HACK! */, 0, acc.get_public_address(), acc.get_public_address(), miner_tx, get_tx_version(height, m_hardforks), currency::blobdata(), 1); + uint64_t block_reward_without_fee = 0; + + r = construct_miner_tx(height, epee::misc_utils::median(block_sizes), already_generated_coins, 0 /* current_block_size !HACK! */, 0, acc.get_public_address(), acc.get_public_address(), miner_tx, block_reward_without_fee, get_tx_version(height, m_hardforks), currency::blobdata(), 1); CHECK_AND_ASSERT_MES(r, false, "construct_miner_tx failed"); if (!cb(miner_tx)) diff --git a/tests/core_tests/pos_block_builder.cpp b/tests/core_tests/pos_block_builder.cpp index e4a021e0..60f7a3bf 100644 --- a/tests/core_tests/pos_block_builder.cpp +++ b/tests/core_tests/pos_block_builder.cpp @@ -166,9 +166,10 @@ void pos_block_builder::step4_generate_coinbase_tx(size_t median_size, pe.amount = m_context.stake_amount; // generate miner tx using incorrect current_block_size only for size estimation + uint64_t block_reward_without_fee = 0; size_t estimated_block_size = m_txs_total_size; bool r = construct_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee, - reward_receiver_address, stakeholder_address, m_block.miner_tx, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); + reward_receiver_address, stakeholder_address, m_block.miner_tx, block_reward_without_fee, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); CHECK_AND_ASSERT_THROW_MES(r, "construct_miner_tx failed"); estimated_block_size = m_txs_total_size + get_object_blobsize(m_block.miner_tx); @@ -176,7 +177,7 @@ void pos_block_builder::step4_generate_coinbase_tx(size_t median_size, for (size_t try_count = 0; try_count != 10; ++try_count) { r = construct_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee, - reward_receiver_address, stakeholder_address, m_block.miner_tx, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); + reward_receiver_address, stakeholder_address, m_block.miner_tx, estimated_block_size, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); CHECK_AND_ASSERT_THROW_MES(r, "construct_homemade_pos_miner_tx failed"); cumulative_size = m_txs_total_size + get_object_blobsize(m_block.miner_tx); diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index cd8643ca..60c37c98 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -34,23 +34,24 @@ bool test_transaction_generation_and_ring_signature() std::string add_str = miner_acc3.get_public_address_str(); + uint64_t block_reward_without_fee = 0; account_base rv_acc; rv_acc.generate(); account_base rv_acc2; rv_acc2.generate(); transaction tx_mine_1; - construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().account_address, miner_acc1.get_keys().account_address, tx_mine_1, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().account_address, miner_acc1.get_keys().account_address, tx_mine_1, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); transaction tx_mine_2; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc2.get_keys().account_address, miner_acc2.get_keys().account_address, tx_mine_2, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc2.get_keys().account_address, miner_acc2.get_keys().account_address, tx_mine_2, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); transaction tx_mine_3; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc3.get_keys().account_address, miner_acc3.get_keys().account_address, tx_mine_3, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc3.get_keys().account_address, miner_acc3.get_keys().account_address, tx_mine_3, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); transaction tx_mine_4; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc4.get_keys().account_address, miner_acc4.get_keys().account_address, tx_mine_4, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc4.get_keys().account_address, miner_acc4.get_keys().account_address, tx_mine_4, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); transaction tx_mine_5; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc5.get_keys().account_address, miner_acc5.get_keys().account_address, tx_mine_5, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc5.get_keys().account_address, miner_acc5.get_keys().account_address, tx_mine_5, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); transaction tx_mine_6; - construct_miner_tx(0, 0, 0, 0, 0, miner_acc6.get_keys().account_address, miner_acc6.get_keys().account_address, tx_mine_6, TRANSACTION_VERSION_PRE_HF4); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc6.get_keys().account_address, miner_acc6.get_keys().account_address, tx_mine_6, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); //fill inputs entry typedef tx_source_entry::output_entry tx_output_entry; @@ -134,8 +135,9 @@ bool test_block_creation() account_public_address adr; bool r = get_account_address_from_str(adr, "ZxDLGBGXbjo5w51tJkvxEPHFRr7Xft4hf33N8EkJPndoGCqocQF1mzpZqYwXByx5gMbfQuPAAB9vj79EFR6Jwkgu1o3aMQPwJ"); CHECK_AND_ASSERT_MES(r, false, "failed to import"); + uint64_t block_reward_without_fee = 0; block b; - r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, adr, b.miner_tx, TRANSACTION_VERSION_PRE_HF4); + r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, adr, b.miner_tx, block_reward_without_fee, TRANSACTION_VERSION_PRE_HF4); return r; } diff --git a/tests/core_tests/zarcanum_test.cpp b/tests/core_tests/zarcanum_test.cpp index 0c7d467d..245eb085 100644 --- a/tests/core_tests/zarcanum_test.cpp +++ b/tests/core_tests/zarcanum_test.cpp @@ -807,7 +807,7 @@ bool zarcanum_block_with_txs::generate(std::vector& events) co // make sure Alice received both block reward and the fee uint64_t mined_amount_2 = COIN + fee; - DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, m_alice_balance + mined_amount_2, 0, mined_amount + mined_amount_2, 0, 0)); + DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, m_alice_balance + mined_amount_2, UINT64_MAX, mined_amount + mined_amount_2, 0, 0)); m_alice_balance += mined_amount_2; From ce0f587c53c7c96f5f7d94fd07240e0ec2587391 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 9 Jun 2023 14:15:40 +0200 Subject: [PATCH 03/18] coretests: fixed a small error in pos_block_builder --- tests/core_tests/pos_block_builder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/pos_block_builder.cpp b/tests/core_tests/pos_block_builder.cpp index 60f7a3bf..d267ffe5 100644 --- a/tests/core_tests/pos_block_builder.cpp +++ b/tests/core_tests/pos_block_builder.cpp @@ -177,7 +177,7 @@ void pos_block_builder::step4_generate_coinbase_tx(size_t median_size, for (size_t try_count = 0; try_count != 10; ++try_count) { r = construct_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee, - reward_receiver_address, stakeholder_address, m_block.miner_tx, estimated_block_size, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); + reward_receiver_address, stakeholder_address, m_block.miner_tx, block_reward_without_fee, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use); CHECK_AND_ASSERT_THROW_MES(r, "construct_homemade_pos_miner_tx failed"); cumulative_size = m_txs_total_size + get_object_blobsize(m_block.miner_tx); From eee04b65aee9690947301c29d07c576319dc9bd2 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 9 Jun 2023 19:44:27 +0200 Subject: [PATCH 04/18] wallet: fixed defragmentation tx minimum utxo limit --- src/wallet/wallet2.cpp | 38 +++++++++++++++++++++++++++++++------- src/wallet/wallet2.h | 1 + 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 39d669ed..c0175337 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -51,8 +51,9 @@ using namespace currency; #define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB -#define WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX 10 // TODO @#@# change to default decoy set number +#define WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX 10 // TODO @#@# change to default decoy set number #define WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 100 // TODO: @#@# consider descreasing to mimic normal tx +#define WALLET_MAX_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 100 // TODO: @#@# consider descreasing to mimic normal tx #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" @@ -73,6 +74,7 @@ namespace tools , m_last_pow_block_h(0) , m_minimum_height(WALLET_MINIMUM_HEIGHT_UNSET_CONST) , m_min_utxo_count_for_defragmentation_tx(WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX) + , m_max_utxo_count_for_defragmentation_tx(WALLET_MAX_UTXO_COUNT_FOR_DEFRAGMENTATION_TX) , m_decoys_count_for_defragmentation_tx(WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX) , m_current_wallet_file_size(0) , m_use_deffered_global_outputs(false) @@ -3372,8 +3374,16 @@ bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::tran construct_tx_param ctp = get_default_construct_tx_param(); ctp.create_utxo_defragmentation_tx = true; - TRY_ENTRY(); - transfer(ctp, tx, false, nullptr); + try + { + transfer(ctp, tx, false, nullptr); + } + catch(error::wallet_error& we) + { + if (we.error_code() == API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION) + return false; + WLT_LOG_ERROR("generate_utxo_defragmentation_transaction_if_needed: transfer failed with: " << we.what()); + //} <-- this brace is inside CATCH_ENTRY2 CATCH_ENTRY2(false); return true; @@ -5367,7 +5377,10 @@ bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector= LOG_LEVEL_2) + ss << "preparing sources for utxo defragmentation tx:"; + for (size_t i = 0, size = m_transfers.size(); i < size && selected_indicies.size() < m_max_utxo_count_for_defragmentation_tx; ++i) { const auto& td = m_transfers[i]; if (!td.is_native_coin() || td.m_amount > CURRENCY_BLOCK_REWARD) @@ -5377,10 +5390,21 @@ bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector= LOG_LEVEL_2) + ss << " selected transfer #" << i << ", amount: " << print_money_brief(td.m_amount) << ", height: " << td.m_ptx_wallet_info->m_block_height << ", " << (td.is_zc() ? "ZC" : " "); } } + if (selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx) + { + // too few outputs were found, hence don't create a defragmentation tx + selected_indicies.clear(); + found_money = 0; + return false; + } + + WLT_LOG(ss.str(), LOG_LEVEL_2); + return prepare_tx_sources(m_decoys_count_for_defragmentation_tx, sources, selected_indicies); } //---------------------------------------------------------------------------------------------------- @@ -6429,8 +6453,8 @@ void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx TIME_MEASURE_START_MS(prepare_tx_sources_time); if (ctp.create_utxo_defragmentation_tx) { - bool r = prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount); - WLT_THROW_IF_FALSE_WITH_CODE(r, "utxo defragmentation tx was not prepared (not an error)", API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION); + if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) + throw error::wallet_error(LOCATION_STR, "", API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION); } else if (ctp.htlc_tx_id != currency::null_hash) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6046e736..57e62d79 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1145,6 +1145,7 @@ private: std::atomic m_last_bc_timestamp; bool m_do_rise_transfer; uint64_t m_min_utxo_count_for_defragmentation_tx; + uint64_t m_max_utxo_count_for_defragmentation_tx; size_t m_decoys_count_for_defragmentation_tx; transfer_container m_transfers; From ad01fb0ba7a452200224942d40052c5fae63aff8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 10 Jun 2023 16:07:12 +0200 Subject: [PATCH 05/18] Moved UI to latest commits for testnet --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 2750d12c..6aede2b4 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 2750d12c11f6063e75f6370a4382db7f0784d624 +Subproject commit 6aede2b431d688a799788cb67ad1aea9d9d87be1 From 2955b2c23142bbdf4e372abda3ac97d11f97393b Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 12 Jun 2023 18:37:35 +0200 Subject: [PATCH 06/18] add built html to UI --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 6aede2b4..94dcb948 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 6aede2b431d688a799788cb67ad1aea9d9d87be1 +Subproject commit 94dcb9483a1a9e6ef03fd257fef39e503cef366e From dbb27121193b8f9b656b130e90bef8604ee9206b Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 12 Jun 2023 20:37:26 +0200 Subject: [PATCH 07/18] wallet: minor refactoring around generate_utxo_defragmentation_transaction_if_needed; coretests packing_outputs_on_pos_minting_wallet and pos_minting_tx_packing fixed --- src/common/error_codes.h | 1 - src/currency_core/currency_format_utils.h | 2 ++ src/wallet/wallet2.cpp | 40 +++++++++++++---------- src/wallet/wallet2.h | 5 +-- tests/core_tests/pos_validation.cpp | 2 +- tests/core_tests/wallet_tests.cpp | 3 +- 6 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/common/error_codes.h b/src/common/error_codes.h index 87b7fa8c..313a9aad 100644 --- a/src/common/error_codes.h +++ b/src/common/error_codes.h @@ -12,7 +12,6 @@ #define API_RETURN_CODE_INTERNAL_ERROR "INTERNAL_ERROR" #define API_RETURN_CODE_NOT_ENOUGH_MONEY "NOT_ENOUGH_MONEY" #define API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_MIXING "NOT_ENOUGH_OUTPUTS_FOR_MIXING" -#define API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION "NOT_ENOUGH_OUTPUTS_FOR_OPERATION" #define API_RETURN_CODE_INTERNAL_ERROR_QUE_FULL "INTERNAL_ERROR_QUE_FULL" #define API_RETURN_CODE_BAD_ARG "BAD_ARG" #define API_RETURN_CODE_BAD_ARG_EMPTY_DESTINATIONS "BAD_ARG_EMPTY_DESTINATIONS" diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index bcd15258..20730fd9 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -190,6 +190,7 @@ namespace currency std::string htlc_origin; std::vector> outs_key_images; // pairs (out_index, key_image) for each change output crypto::key_derivation derivation; + bool was_not_prepared = false; // true if tx was not prepared/created for some good reason (e.g. not enough outs for UTXO defragmentation tx). Because we decided not to throw exceptions for non-error cases. -- sowle BEGIN_SERIALIZE_OBJECT() FIELD(tx) @@ -198,6 +199,7 @@ namespace currency FIELD(htlc_origin) FIELD(outs_key_images) FIELD(derivation) + FIELD(was_not_prepared) END_SERIALIZE() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c0175337..a77a9567 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -209,9 +209,15 @@ bool wallet2::set_core_proxy(const std::shared_ptr& proxy) return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_pos_min_utxo_count_for_defragmentation_tx(uint64_t new_size) +void wallet2::set_pos_utxo_count_limits_for_defragmentation_tx(uint64_t min_outs, uint64_t max_outs) { - m_min_utxo_count_for_defragmentation_tx = new_size; + m_min_utxo_count_for_defragmentation_tx = min_outs; + m_max_utxo_count_for_defragmentation_tx = max_outs; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_pos_decoys_count_for_defragmentation_tx(size_t decoys_count) +{ + m_decoys_count_for_defragmentation_tx = decoys_count; } //---------------------------------------------------------------------------------------------------- std::shared_ptr wallet2::get_core_proxy() @@ -3373,19 +3379,14 @@ bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::tran { construct_tx_param ctp = get_default_construct_tx_param(); ctp.create_utxo_defragmentation_tx = true; - - try - { - transfer(ctp, tx, false, nullptr); - } - catch(error::wallet_error& we) - { - if (we.error_code() == API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION) - return false; - WLT_LOG_ERROR("generate_utxo_defragmentation_transaction_if_needed: transfer failed with: " << we.what()); - //} <-- this brace is inside CATCH_ENTRY2 - CATCH_ENTRY2(false); + finalized_tx ftp{}; + transfer(ctp, ftp, false, nullptr); + + if (ftp.was_not_prepared) + return false; // no such UTXO were found, not an error + + tx = ftp.tx; return true; } //---------------------------------------------------------------------------------------------------- @@ -6419,7 +6420,7 @@ void wallet2::prepare_tx_destinations(uint64_t needed_money, } } //---------------------------------------------------------------------------------------------------- -void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) +bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) { SET_CONTEXT_OBJ_FOR_SCOPE(pconstruct_tx_param, ctp); @@ -6454,7 +6455,7 @@ void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx if (ctp.create_utxo_defragmentation_tx) { if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) - throw error::wallet_error(LOCATION_STR, "", API_RETURN_CODE_NOT_ENOUGH_OUTPUTS_FOR_OPERATION); + return false; } else if (ctp.htlc_tx_id != currency::null_hash) { @@ -6510,6 +6511,7 @@ void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx << ", construct_tx_time: " << construct_tx_time << " ms" << ", sign_ms_input_time: " << sign_ms_input_time << " ms", LOG_LEVEL_0);*/ + return true; } //---------------------------------------------------------------------------------------------------- void wallet2::finalize_transaction(const currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key /* = true */) @@ -6718,7 +6720,11 @@ void wallet2::transfer(construct_tx_param& ctp, TIME_MEASURE_START(prepare_transaction_time); currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(ctp, ftp); + if (prepare_transaction(ctp, ftp)) + { + result.was_not_prepared = true; + return; + } TIME_MEASURE_FINISH(prepare_transaction_time); if (m_watch_only) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 57e62d79..9086e1f5 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -607,7 +607,8 @@ namespace tools void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id); bool set_core_proxy(const std::shared_ptr& proxy); - void set_pos_min_utxo_count_for_defragmentation_tx(uint64_t new_size); + void set_pos_utxo_count_limits_for_defragmentation_tx(uint64_t min_outs, uint64_t max_outs); // don't create UTXO defrag. tx if there are less than 'min_outs' outs; don't put more than 'max_outs' outs + void set_pos_decoys_count_for_defragmentation_tx(size_t decoys_count); void set_minimum_height(uint64_t h); std::shared_ptr get_core_proxy(); uint64_t balance() const; @@ -931,7 +932,7 @@ namespace tools const std::list& get_expiration_entries() const { return m_money_expirations; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; - void prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& emode_separate = mode_separate_context()); + bool prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& emode_separate = mode_separate_context()); void finalize_transaction(const currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key = true); void finalize_transaction(const currency::finalize_tx_param& ftp, currency::finalized_tx& result, bool broadcast_tx, bool store_tx_secret_key = true ); diff --git a/tests/core_tests/pos_validation.cpp b/tests/core_tests/pos_validation.cpp index 03434c15..0106b050 100644 --- a/tests/core_tests/pos_validation.cpp +++ b/tests/core_tests/pos_validation.cpp @@ -1085,7 +1085,7 @@ bool pos_minting_tx_packing::c1(currency::core& c, size_t ev_index, const std::v m_alice_start_amount + CURRENCY_BLOCK_REWARD * m_pos_mint_packing_size // unlocked ), false, ""); - alice_wlt->set_pos_min_utxo_count_for_defragmentation_tx(m_pos_mint_packing_size); + alice_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(m_pos_mint_packing_size + 1, m_pos_mint_packing_size + 1); // +1 because previous implementation () had an error with this limit // no coinbase tx outputs should be packed r = alice_wlt->try_mint_pos(); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 4937774a..5fe58dc2 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3402,7 +3402,8 @@ bool packing_outputs_on_pos_minting_wallet::c1(currency::core& c, size_t ev_inde miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5, false, "Incorrect numbers of blocks fetched"); - miner_wlt->set_pos_min_utxo_count_for_defragmentation_tx(4); + miner_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(5, 5); + miner_wlt->set_pos_decoys_count_for_defragmentation_tx(0); CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner_wlt", m_premine_amount + m_mined_amount, uint64_max, uint64_max, 0, 0), false, ""); miner_wlt->try_mint_pos(); From 633d5238ef4f5eced8085f77819672640a938032 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 12 Jun 2023 20:38:43 +0200 Subject: [PATCH 08/18] version bump: 2.0.0.206 -> 2.0.0.207 --- 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 5e1ef50a..f10fbdb2 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 206 +#define PROJECT_VERSION_BUILD_NO 207 #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 "]" From 44ed820abe32ef8860065574f36b78d8243e914f Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 12 Jun 2023 21:41:08 +0200 Subject: [PATCH 09/18] wallet2: fixed a typo --- src/wallet/wallet2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a77a9567..a80daf50 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6720,7 +6720,7 @@ void wallet2::transfer(construct_tx_param& ctp, TIME_MEASURE_START(prepare_transaction_time); currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); ftp.tx_version = this->get_current_tx_version(); - if (prepare_transaction(ctp, ftp)) + if (!prepare_transaction(ctp, ftp)) { result.was_not_prepared = true; return; From ba594dd5ace69be91935450b7879c322ffd27669 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 13 Jun 2023 17:06:56 +0200 Subject: [PATCH 10/18] additional error handling for wallet2 and simplewallet --- src/simplewallet/simplewallet.cpp | 8 ++++++++ src/wallet/wallet2.cpp | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 0b0292c3..3e52378a 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1808,11 +1808,13 @@ bool simple_wallet::deploy_new_asset(const std::vector &args) if (!args.size() || args.size() > 1) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } bool r = epee::serialization::load_t_from_json_file(adb, args[0]); if (!r) { fail_msg_writer() << "Failed to load json file with asset specification: " << args[0]; + return true; } tx_destination_entry td = AUTO_VAL_INIT(td); td.addr.push_back(m_wallet->get_account().get_public_address()); @@ -1842,6 +1844,7 @@ bool simple_wallet::add_custom_asset_id(const std::vector &args) if (!args.size() || args.size() > 1) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } crypto::public_key asset_id = currency::null_pkey; if (!epee::string_tools::parse_tpod_from_hex_string(args[0], asset_id)) @@ -1877,6 +1880,7 @@ bool simple_wallet::generate_ionic_swap_proposal(const std::vector if (args.size() != 2) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } view::ionic_swap_proposal_info proposal_info = AUTO_VAL_INIT(proposal_info); @@ -1884,6 +1888,7 @@ bool simple_wallet::generate_ionic_swap_proposal(const std::vector if (!r) { fail_msg_writer() << "Failed to load json file with asset specification: " << args[0]; + return true; } currency::account_public_address destination_addr = AUTO_VAL_INIT(destination_addr); currency::payment_id_t integrated_payment_id; @@ -1921,6 +1926,7 @@ bool simple_wallet::get_ionic_swap_proposal_info(const std::vector if (args.size() != 1) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } std::string raw_proposal; @@ -1953,6 +1959,7 @@ bool simple_wallet::accept_ionic_swap_proposal(const std::vector &a if (args.size() != 1) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } std::string raw_proposal; @@ -1983,6 +1990,7 @@ bool simple_wallet::remove_custom_asset_id(const std::vector &args) if (!args.size() || args.size() > 1) { fail_msg_writer() << "invalid arguments count: " << args.size() << ", expected 1"; + return true; } crypto::public_key asset_id = currency::null_pkey; if (!epee::string_tools::parse_tpod_from_hex_string(args[0], asset_id)) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a80daf50..5d1ca212 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6712,6 +6712,10 @@ void wallet2::transfer(construct_tx_param& ctp, check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); + bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; + bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_operation_requested || dont_have_zero_asset_ids_in_destinations, "zero asset id is used errounesly (no asset operation was requested)"); + if (ctp.crypt_address.spend_public_key == currency::null_pkey) { ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), ctp.dsts); From b92f63e17662975c2540b250001cef1406d67614 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 13 Jun 2023 17:08:28 +0200 Subject: [PATCH 11/18] version bump: 2.0.0.207 -> 2.0.0.208 --- 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 f10fbdb2..f4a3853a 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 207 +#define PROJECT_VERSION_BUILD_NO 208 #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 "]" From ee5c0fe787533675c3dcae30cb6cae55d8b21297 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 15:34:41 +0200 Subject: [PATCH 12/18] coretests: packing_outputs_on_pos_minting_wallet were completely rewritten to uncover two rare bugs in PoS block construction / defragmentation tx --- tests/core_tests/chaingen_main.cpp | 2 +- tests/core_tests/wallet_tests.cpp | 126 ++++++++++++++++++++--------- tests/core_tests/wallet_tests.h | 5 +- 3 files changed, 93 insertions(+), 40 deletions(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 5d1c1851..3590c2d7 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1076,7 +1076,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(wallet_outputs_with_same_key_image); GENERATE_AND_PLAY(wallet_unconfirmed_tx_expiration); GENERATE_AND_PLAY(wallet_unconfimed_tx_balance); - GENERATE_AND_PLAY_HF(packing_outputs_on_pos_minting_wallet, "3"); + GENERATE_AND_PLAY_HF(packing_outputs_on_pos_minting_wallet, "3-*"); GENERATE_AND_PLAY(wallet_watch_only_and_chain_switch); GENERATE_AND_PLAY(wallet_rpc_integrated_address); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 5fe58dc2..be3fcebb 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3355,15 +3355,15 @@ packing_outputs_on_pos_minting_wallet::packing_outputs_on_pos_minting_wallet() bool packing_outputs_on_pos_minting_wallet::generate(std::vector& events) const { + // Test ideas: + // 1) UTXO defragmentation tx cannot select stake output by accident; + // 2) if UTXO defragmentation tx cannot be constructed because of too few decoy outputs, PoS can still be created; + // 3) UTXO defragmentation tx limits works as expected. - // 0 10 11 21 22 <- blockchain height (assuming CURRENCY_MINED_MONEY_UNLOCK_WINDOW == 10) - // (0 )... (0r)- (1 )... (1r)- <- main chain - // tx_0 <- txs - - GENERATE_ACCOUNT(miner_acc); - m_accounts.push_back(miner_acc); - //GENERATE_ACCOUNT(alice_acc); - //m_accounts.push_back(alice_acc); + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); + account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); // don't use MAKE_GENESIS_BLOCK here because it will mask 'generator' currency::block blk_0 = AUTO_VAL_INIT(blk_0); @@ -3372,21 +3372,32 @@ bool packing_outputs_on_pos_minting_wallet::generate(std::vectorbalance(unlocked, awaiting_in, awaiting_out, mined); + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); - size_t n_blocks = CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5; - REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, n_blocks); - m_mined_amount = n_blocks * COIN; + m_single_amount = TESTS_DEFAULT_FEE * 5; - REFRESH_TEST_WALLET_AT_GEN_TIME(events, miner_wlt, blk_0r, n_blocks); - CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(miner_wlt, m_premine_amount + m_mined_amount); + MAKE_TX(events, tx_0, miner_acc, alice_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_1, miner_acc, alice_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_2, miner_acc, alice_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_3, miner_acc, alice_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_4, miner_acc, alice_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_5, miner_acc, bob_acc, m_single_amount, blk_0r); + MAKE_TX(events, tx_6, miner_acc, bob_acc, m_single_amount, blk_0r); - //MAKE_TX_FEE(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(2000), TESTS_DEFAULT_FEE, blk_0r); - //MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); - //REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + m_alice_initial_balance = m_single_amount * 5; + m_bob_initial_balance = m_single_amount * 2; + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({tx_0, tx_1, tx_2, tx_3, tx_4, tx_5, tx_6})); + + REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0); + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_1r, CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 1); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, m_alice_initial_balance); + + CREATE_TEST_WALLET(bob_wlt, bob_acc, blk_0); + REFRESH_TEST_WALLET_AT_GEN_TIME(events, bob_wlt, blk_1r, CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 1); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(bob_wlt, m_bob_initial_balance); DO_CALLBACK(events, "c1"); @@ -3395,31 +3406,72 @@ bool packing_outputs_on_pos_minting_wallet::generate(std::vector& events) { - std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); size_t blocks_fetched = 0; bool received_money; std::atomic atomic_false = ATOMIC_VAR_INIT(false); - miner_wlt->refresh(blocks_fetched, received_money, atomic_false); - CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5, false, "Incorrect numbers of blocks fetched"); + + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 1, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_alice_initial_balance, 0, m_alice_initial_balance, 0, 0), false, ""); + + bob_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 1, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt.get(), "bob_wlt", m_bob_initial_balance, 0, m_bob_initial_balance, 0, 0), false, ""); + + // 1. Try to defragment the same UTXO that is used for staking + // (Bob has two: one UTXO is for staking, other is being defragmented) + bob_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(1, 10); + bob_wlt->set_pos_decoys_count_for_defragmentation_tx(0); + bob_wlt->try_mint_pos(); + + CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 3, false, "Incorrect blockchain height:" << c.get_current_blockchain_size()); + bob_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == 1, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); + + // (also make sure that unlocked balance is zero) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt.get(), "bob_wlt", m_bob_initial_balance + CURRENCY_BLOCK_REWARD, CURRENCY_BLOCK_REWARD + TESTS_DEFAULT_FEE, 0), false, ""); + - miner_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(5, 5); - miner_wlt->set_pos_decoys_count_for_defragmentation_tx(0); - CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner_wlt", m_premine_amount + m_mined_amount, uint64_max, uint64_max, 0, 0), false, ""); + // 2. Try to mine a PoS block and defragment some of UTXO + alice_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(2, 2); + alice_wlt->set_pos_decoys_count_for_defragmentation_tx(0); + alice_wlt->try_mint_pos(); - miner_wlt->try_mint_pos(); + CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 4, false, "Incorrect blockchain height:" << c.get_current_blockchain_size()); + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == 2, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); - CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 7, false, "Incorrect blockchain height:" << c.get_current_blockchain_size()); - miner_wlt->refresh(blocks_fetched, received_money, atomic_false); - CHECK_AND_ASSERT_MES(blocks_fetched == 1, false, "Incorrect numbers of blocks fetched"); + // (also make sure that only two UTXOs is unlocked) + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_alice_initial_balance + CURRENCY_BLOCK_REWARD, CURRENCY_BLOCK_REWARD + TESTS_DEFAULT_FEE, m_single_amount * 2), false, ""); - block top_block = AUTO_VAL_INIT(top_block); - bool r = c.get_blockchain_storage().get_top_block(top_block); - CHECK_AND_ASSERT_MES(r && is_pos_block(top_block), false, "get_top_block failed or smth goes wrong"); - uint64_t top_block_reward = get_outs_money_amount(top_block.miner_tx); - CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner_wlt", m_premine_amount + m_mined_amount + COIN), false, ""); - miner_wlt->reset_password(g_wallet_password); - miner_wlt->store(g_wallet_filename); + // 3. Try to mine a PoS block and defragment with huge decoy set. Make sure block is mined successfully without a defragmentation tx + // Alice has one UTXO + alice_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(1, 1); + alice_wlt->set_pos_decoys_count_for_defragmentation_tx(80); + alice_wlt->try_mint_pos(); + + CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 5, false, "Incorrect blockchain height:" << c.get_current_blockchain_size()); + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == 1, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); + + // Alice's unlocked balance should consist only of one UTXO + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_alice_initial_balance + CURRENCY_BLOCK_REWARD * 2, CURRENCY_BLOCK_REWARD * 2 + TESTS_DEFAULT_FEE, m_single_amount), false, ""); + + + // 4. Finally mine a PoS and defragment the last one unlocked UTXO + alice_wlt->set_pos_utxo_count_limits_for_defragmentation_tx(1, 1); + alice_wlt->set_pos_decoys_count_for_defragmentation_tx(0); + alice_wlt->try_mint_pos(); + + CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 6, false, "Incorrect blockchain height:" << c.get_current_blockchain_size()); + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == 1, false, "Incorrect numbers of blocks fetched: " << blocks_fetched); + + // Alice's unlocked balance should be zero + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_alice_initial_balance + CURRENCY_BLOCK_REWARD * 3, CURRENCY_BLOCK_REWARD * 3 + TESTS_DEFAULT_FEE, 0), false, ""); return true; } diff --git a/tests/core_tests/wallet_tests.h b/tests/core_tests/wallet_tests.h index 46905ead..967908b8 100644 --- a/tests/core_tests/wallet_tests.h +++ b/tests/core_tests/wallet_tests.h @@ -257,8 +257,9 @@ struct packing_outputs_on_pos_minting_wallet : public wallet_test bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); - mutable uint64_t m_premine_amount = 0; - mutable uint64_t m_mined_amount = 0; + mutable uint64_t m_single_amount = 0; + mutable uint64_t m_alice_initial_balance = 0; + mutable uint64_t m_bob_initial_balance = 0; }; struct wallet_sending_to_integrated_address : public wallet_test From 50afdacd39f7422fcc196e541f883f46a56847c3 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 16:43:28 +0200 Subject: [PATCH 13/18] wallet2: fixed a bug preventing PoS block creation when the UTXO defragmentation tx lacks decoy outputs --- src/wallet/wallet2.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5d1ca212..e0da26fc 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6454,8 +6454,12 @@ bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx TIME_MEASURE_START_MS(prepare_tx_sources_time); if (ctp.create_utxo_defragmentation_tx) { - if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) - return false; + try + { + if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) + return false; + } + catch(const error::not_enough_outs_to_mix&) { return false; } // if there's not enough decoys, return false to indicate minor non-fatal error } else if (ctp.htlc_tx_id != currency::null_hash) { From 606ce9316915cfd9877cc1e52b5e951e99125a81 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 17:09:33 +0200 Subject: [PATCH 14/18] wallet2: fixed a rare bug preventing PoS block creation when UTXO defragmentation tx accidently uses stake output --- src/wallet/wallet2.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e0da26fc..4b93bf73 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4224,6 +4224,15 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::acco tmpl_req.pe.tx_out_index = td.m_internal_output_index; tmpl_req.pe.wallet_index = cxt.index; + // mark stake source as spent and make sure it will be restored in case of error + const std::vector stake_transfer_idx_vec{ cxt.index }; + mark_transfers_as_spent(stake_transfer_idx_vec, "stake source"); + bool gracefull_leaving = false; + auto stake_transfer_spent_flag_restorer = epee::misc_utils::create_scope_leave_handler([&](){ + if (!gracefull_leaving) + clear_transfers_from_flag(stake_transfer_idx_vec, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "stake source"); + }); + // generate UTXO Defragmentation Transaction - to reduce the UTXO set size transaction udtx{}; if (generate_utxo_defragmentation_transaction_if_needed(udtx)) @@ -4258,7 +4267,7 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::acco WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); crypto::hash block_hash = get_block_hash(b); - WLT_LOG_GREEN("Block " << print16(block_hash) << " has been constructed, sending to core...", LOG_LEVEL_0); + WLT_LOG_GREEN("Block " << print16(block_hash) << " @ " << get_block_height(b) << " has been constructed, sending to core...", LOG_LEVEL_0); currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); @@ -4275,6 +4284,7 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::acco WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); m_wcallback->on_pos_block_found(b); + gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler return true; } //---------------------------------------------------------------------------------------------------- From 5620e453b6f7557da540e62d2a1db6cfdca2d813 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 19:47:07 +0200 Subject: [PATCH 15/18] coretests: added new test asset_depoyment_and_few_zc_utxos which uncovers a rare bug in asset deployment that occurs when there are few ZC outputs --- tests/core_tests/chaingen_main.cpp | 1 + tests/core_tests/multiassets_test.cpp | 126 ++++++++++++++++++++++++++ tests/core_tests/multiassets_test.h | 9 ++ 3 files changed, 136 insertions(+) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 3590c2d7..7cc413d3 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1256,6 +1256,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(zarcanum_in_alt_chain); GENERATE_AND_PLAY(assets_and_explicit_native_coins_in_outs); GENERATE_AND_PLAY(zarcanum_block_with_txs); + GENERATE_AND_PLAY(asset_depoyment_and_few_zc_utxos); // GENERATE_AND_PLAY(gen_block_reward); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index 8c31dbb5..faff7b93 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -332,3 +332,129 @@ bool assets_and_explicit_native_coins_in_outs::c2_alice_deploys_asset(currency:: return true; } + +//------------------------------------------------------------------------------ + +asset_depoyment_and_few_zc_utxos::asset_depoyment_and_few_zc_utxos() +{ + REGISTER_CALLBACK_METHOD(asset_depoyment_and_few_zc_utxos, c1); + + m_hardforks.clear(); + m_hardforks.set_hardfork_height(ZANO_HARDFORK_04_ZARCANUM, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); +} + +bool asset_depoyment_and_few_zc_utxos::generate(std::vector& events) const +{ + bool r = false; + + 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 + + // HF4 requires tests_random_split_strategy (for 2 outputs minimum) + test_gentime_settings tgts = generator.get_test_gentime_settings(); + tgts.split_strategy = tests_random_split_strategy; + generator.set_test_gentime_settings(tgts); + + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + DO_CALLBACK_PARAMS(events, "check_hardfork_inactive", static_cast(ZANO_HARDFORK_04_ZARCANUM)); + + // tx_0: miner -> Alice + // make tx_0 before HF4, so Alice will have only bare outs + transaction tx_0{}; + std::vector sources; + std::vector destinations; + for(size_t i = 0; i < 100; ++i) + destinations.emplace_back(TESTS_DEFAULT_FEE, m_accounts[ALICE_ACC_IDX].get_public_address()); + m_alice_initial_balance = TESTS_DEFAULT_FEE * 100; + r = fill_tx_sources(sources, events, blk_0r, miner_acc.get_keys(), m_alice_initial_balance + TESTS_DEFAULT_FEE, 0); + CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); + r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_0, get_tx_version_from_events(events), 0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); + + ADD_CUSTOM_EVENT(events, tx_0); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); + + // make sure HF4 has been activated + DO_CALLBACK_PARAMS(events, "check_hardfork_active", static_cast(ZANO_HARDFORK_04_ZARCANUM)); + + // tx_1: miner -> Alice + // send less than min fee. This output will be the only ZC UTXO in Alice's wallet + //destinations.clear(); + + MAKE_TX(events, tx_1, miner_acc, alice_acc, TESTS_DEFAULT_FEE * 0.5, blk_1); + m_alice_initial_balance += TESTS_DEFAULT_FEE * 0.5; + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_1); + + // rewind blocks + REWIND_BLOCKS_N_WITH_TIME(events, blk_2r, blk_2, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // check Alice's balance and make sure she can deploy an asset + DO_CALLBACK(events, "c1"); + + return true; +} + +bool asset_depoyment_and_few_zc_utxos::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + alice_wlt->refresh(); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", m_alice_initial_balance, 0, m_alice_initial_balance, 0, 0), false, ""); + + // make sure Alice has correct UTXO wallet structure + tools::wallet2::transfer_container transfers{}; + alice_wlt->get_transfers(transfers); + size_t zc_unspent_outs = 0, unspent_outs = 0; + for(auto& td : transfers) + { + if (!td.is_spent()) + { + ++unspent_outs; + if (td.is_zc()) + ++zc_unspent_outs; + } + } + CHECK_AND_ASSERT_MES(unspent_outs == 101 && zc_unspent_outs == 1, false, "incorrect UTXO structure: " << unspent_outs << ", " << zc_unspent_outs); + + asset_descriptor_base adb{}; + adb.total_max_supply = 100 * 1000000000000; + adb.full_name = "very confidential asset"; + adb.ticker = "VCA"; + adb.decimal_point = 12; + + std::vector destinations; + destinations.emplace_back(adb.total_max_supply, m_accounts[MINER_ACC_IDX].get_public_address(), null_pkey); + destinations.emplace_back(adb.total_max_supply / 2, m_accounts[MINER_ACC_IDX].get_public_address(), null_pkey); + + transaction asset_emission_tx{}; + crypto::public_key asset_id = null_pkey; + + alice_wlt->deploy_new_asset(adb, destinations, asset_emission_tx, asset_id); + + // make sure the emission tx is correct + CHECK_AND_ASSERT_MES(asset_emission_tx.vout.size() > 2, false, "Unexpected vout size: " << asset_emission_tx.vout.size()); + for(auto& out : asset_emission_tx.vout) + { + CHECK_AND_ASSERT_MES(out.type() == typeid(tx_out_zarcanum), false, "invalid out type"); + const tx_out_zarcanum& out_zc = boost::get(out); + // as soon as this is the asset emmiting transaction, no output has an obvious asset id + // make sure it is so + CHECK_AND_ASSERT_MES(out_zc.blinded_asset_id != native_coin_asset_id_1div8, false, "One of outputs has explicit native asset id, which is unexpected"); + } + + // get this tx confirmed + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + return true; +} diff --git a/tests/core_tests/multiassets_test.h b/tests/core_tests/multiassets_test.h index fbfeb4b3..1f8ecbd0 100644 --- a/tests/core_tests/multiassets_test.h +++ b/tests/core_tests/multiassets_test.h @@ -26,3 +26,12 @@ struct assets_and_explicit_native_coins_in_outs : public wallet_test mutable uint64_t m_alice_initial_balance = 0; }; +struct asset_depoyment_and_few_zc_utxos : public wallet_test +{ + asset_depoyment_and_few_zc_utxos(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); + + mutable uint64_t m_alice_initial_balance = 0; +}; + From dd80d202add5e7eb7ec110d198fd86abc922982c Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 20:06:05 +0200 Subject: [PATCH 16/18] wallet2: fix for a rare bug in asset deployment that occurs when there are few ZC outputs --- src/wallet/wallet2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4b93bf73..71f859d0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4499,6 +4499,7 @@ void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info construct_tx_param ctp = get_default_construct_tx_param(); ctp.dsts = destinations; ctp.extra.push_back(asset_reg_info); + ctp.need_at_least_1_zc = true; finalized_tx ft = AUTO_VAL_INIT(ft); this->transfer(ctp, ft, true, nullptr); From bd893d9fa664a7b43d9610133b59367fcafb8f56 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 15 Jun 2023 21:02:22 +0200 Subject: [PATCH 17/18] wallet2: decrease UTXO defrag limits a bit --- src/wallet/wallet2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 71f859d0..901fb512 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -52,8 +52,8 @@ using namespace currency; #define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB #define WALLET_DEFAULT_DECOYS_COUNT_FOR_DEFRAGMENTATION_TX 10 // TODO @#@# change to default decoy set number -#define WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 100 // TODO: @#@# consider descreasing to mimic normal tx -#define WALLET_MAX_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 100 // TODO: @#@# consider descreasing to mimic normal tx +#define WALLET_MIN_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 3 // TODO: @#@# consider descreasing to mimic normal tx +#define WALLET_MAX_UTXO_COUNT_FOR_DEFRAGMENTATION_TX 10 // TODO: @#@# consider descreasing to mimic normal tx #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" From 26be84780a5218845adcc79680d94502d5890ce5 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 20 Jun 2023 15:09:23 +0200 Subject: [PATCH 18/18] Moved UI to latest commit(and built html) --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 94dcb948..1471e71f 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 94dcb9483a1a9e6ef03fd257fef39e503cef366e +Subproject commit 1471e71f4ff685dd080e6551773cf129f1d02c43