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..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() }; @@ -255,7 +257,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/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 2750d12c..1471e71f 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 2750d12c11f6063e75f6370a4382db7f0784d624 +Subproject commit 1471e71f4ff685dd080e6551773cf129f1d02c43 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/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/version.h.in b/src/version.h.in index 5e1ef50a..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 206 +#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 "]" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 05fe16f5..901fb512 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -51,6 +51,10 @@ 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 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" ENABLE_CHANNEL_BY_DEFAULT("wallet") @@ -69,7 +73,9 @@ 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_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) #ifdef DISABLE_TOR @@ -203,9 +209,15 @@ 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_utxo_count_limits_for_defragmentation_tx(uint64_t min_outs, uint64_t max_outs) { - m_pos_mint_packing_size = 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() @@ -3363,33 +3375,18 @@ 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; - - TRY_ENTRY(); - transfer(ctp, tx, false, nullptr); - CATCH_ENTRY2(false); + ctp.create_utxo_defragmentation_tx = true; + 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; } //---------------------------------------------------------------------------------------------------- @@ -3682,7 +3679,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 +3702,7 @@ void wallet2::get_recent_transfers_history(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)) { - tx_to_blob(pack_tx, tmpl_req.explicit_transaction); - WLT_LOG_GREEN("Packing inputs: " << pack_tx.vin.size() << " inputs consolidated in tx " << get_transaction_hash(pack_tx), LOG_LEVEL_0); + tx_to_blob(udtx, tmpl_req.explicit_transaction); + WLT_LOG_GREEN("Note: " << udtx.vin.size() << " inputs were aggregated into UTXO defragmentation tx " << get_transaction_hash(udtx), LOG_LEVEL_0); } m_core_proxy->call_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!"); @@ -4257,11 +4262,12 @@ 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); - 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); @@ -4278,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; } //---------------------------------------------------------------------------------------------------- @@ -4492,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); @@ -5376,33 +5384,40 @@ 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; ) + std::stringstream ss; + if (epee::log_space::log_singletone::get_log_detalisation_level() >= 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) { - 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); + if (epee::log_space::log_singletone::get_log_detalisation_level() >= 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); } //---------------------------------------------------------------------------------------------------- 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) @@ -6416,7 +6431,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); @@ -6448,9 +6463,14 @@ 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); + 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) { @@ -6506,6 +6526,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 */) @@ -6706,6 +6727,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); @@ -6714,7 +6739,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 6e834fba..9086e1f5 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,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_mint_packing_size(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; @@ -885,7 +884,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, @@ -933,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 ); @@ -1042,7 +1041,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 +1109,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 +1145,9 @@ 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; + uint64_t m_max_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/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/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 5d1c1851..7cc413d3 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); @@ -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; +}; + diff --git a/tests/core_tests/pos_block_builder.cpp b/tests/core_tests/pos_block_builder.cpp index e4a021e0..d267ffe5 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, 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); diff --git a/tests/core_tests/pos_validation.cpp b/tests/core_tests/pos_validation.cpp index 941c7b4d..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_mint_packing_size(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/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/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index a342303b..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,30 +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_mint_packing_size(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, ""); + // 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 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;