diff --git a/contrib/epee/include/serialization/keyvalue_helpers.h b/contrib/epee/include/serialization/keyvalue_helpers.h index 61882816..9d32d024 100644 --- a/contrib/epee/include/serialization/keyvalue_helpers.h +++ b/contrib/epee/include/serialization/keyvalue_helpers.h @@ -51,6 +51,7 @@ namespace epee } }; + //basic helpers for pod-to-hex serialization template std::string transform_t_pod_to_str(const t_pod_type & a) diff --git a/contrib/epee/include/serialization/keyvalue_hexemizer.h b/contrib/epee/include/serialization/keyvalue_hexemizer.h new file mode 100644 index 00000000..47e7e341 --- /dev/null +++ b/contrib/epee/include/serialization/keyvalue_hexemizer.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2019, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Andrey N. Sabelnikov nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#pragma once +#include "keyvalue_serialization.h" +namespace epee +{ + + struct hexemizer + { + std::string blob; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_BLOB_AS_HEX_STRING(blob) + END_KV_SERIALIZE_MAP() + }; + + +} \ No newline at end of file diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index fcab2571..7ecc4a3a 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1358,8 +1358,8 @@ bool blockchain_storage::create_block_template(const create_block_template_param CRITICAL_REGION_END(); - size_t txs_size; - uint64_t fee; + size_t txs_size = 0; + uint64_t fee = 0; bool block_filled = false; if (pcustom_fill_block_template_func == nullptr) block_filled = m_tx_pool.fill_block_template(b, pos, median_size, already_generated_coins, txs_size, fee, height, params.explicit_txs); diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 8a3045b1..09eb40b0 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -1138,6 +1138,7 @@ namespace currency // add explicit transactions for (const auto& tx : explicit_txs) { + fee += get_tx_fee(tx); bl.tx_hashes.push_back(get_transaction_hash(tx)); } return true; diff --git a/src/gui/qt-daemon/application/core_fast_rpc_proxy.h b/src/gui/qt-daemon/application/core_fast_rpc_proxy.h index ca189240..c26c1fb2 100644 --- a/src/gui/qt-daemon/application/core_fast_rpc_proxy.h +++ b/src/gui/qt-daemon/application/core_fast_rpc_proxy.h @@ -109,6 +109,11 @@ namespace tools return m_rpc.on_submitblock(req, rsp, m_err_stub, m_cntxt_stub); } //------------------------------------------------------------------------------------------------------------------------------ + bool call_COMMAND_RPC_SUBMITBLOCK2(const currency::COMMAND_RPC_SUBMITBLOCK2::request& req, currency::COMMAND_RPC_SUBMITBLOCK2::response& rsp) override + { + return m_rpc.on_submitblock2(req, rsp, m_err_stub, m_cntxt_stub); + } + //------------------------------------------------------------------------------------------------------------------------------ bool call_COMMAND_RPC_GET_POS_MINING_DETAILS(const currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response& rsp) override { return m_rpc.on_get_pos_mining_details(req, rsp, m_cntxt_stub); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5a6e89b9..28706e83 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -890,6 +890,63 @@ namespace currency // + res.status = "OK"; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) + { + CHECK_CORE_READY(); + + + block b = AUTO_VAL_INIT(b); + if (!parse_and_validate_block_from_blob(req.b, b)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Wrong block blob"; + return false; + } + + block_verification_context bvc = AUTO_VAL_INIT(bvc); + for (const auto& txblob : req.explicit_txs) + { + + crypto::hash tx_hash = AUTO_VAL_INIT(tx_hash); + transaction tx = AUTO_VAL_INIT(tx); + if (!parse_and_validate_tx_from_blob(txblob.blob, tx, tx_hash)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Wrong explicit tx blob"; + return false; + } + bvc.m_onboard_transactions[tx_hash] = tx; + } + + + if (!m_core.handle_block_found(b, &bvc)) + { + if (bvc.m_added_to_altchain) + { + error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_ADDED_AS_ALTERNATIVE; + error_resp.message = "Block added as alternative"; + return false; + } + error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; + error_resp.message = "Block not accepted"; + return false; + } + //@#@ + //temporary double check timestamp + if (time(NULL) - get_actual_timestamp(b) > 5) + { + LOG_PRINT_RED_L0("Found block (" << get_block_hash(b) << ") timestamp (" << get_actual_timestamp(b) + << ") is suspiciously less (" << time(NULL) - get_actual_timestamp(b) << ") then curren time( " << time(NULL) << ")"); + //mark node to make it easier to find it via scanner + m_core.get_blockchain_storage().get_performnce_data().epic_failure_happend = true; + } + // + + res.status = "OK"; return true; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 287d16d2..03851812 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -63,6 +63,7 @@ namespace currency bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); + bool on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); @@ -125,6 +126,7 @@ namespace currency MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) + MAP_JON_RPC_WE("submitblock2", on_submitblock2, COMMAND_RPC_SUBMITBLOCK2) MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) MAP_JON_RPC_WE("getblockheaderbyhash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) MAP_JON_RPC_WE("getblockheaderbyheight", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 86fd7506..c0cb4990 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -5,6 +5,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once +#include "serialization/keyvalue_hexemizer.h" #include "currency_protocol/currency_protocol_defs.h" #include "currency_core/currency_basic.h" #include "currency_core/difficulty.h" @@ -825,6 +826,29 @@ namespace currency }; }; + struct COMMAND_RPC_SUBMITBLOCK2 + { + struct request + { + std::string b; //hex encoded block blob + std::list explicit_txs; //hex encoded tx blobs + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_BLOB_AS_HEX_STRING(b) + KV_SERIALIZE(explicit_txs) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; + struct block_header_response { uint8_t major_version; diff --git a/src/wallet/core_default_rpc_proxy.cpp b/src/wallet/core_default_rpc_proxy.cpp index 6140270d..ae6123bd 100644 --- a/src/wallet/core_default_rpc_proxy.cpp +++ b/src/wallet/core_default_rpc_proxy.cpp @@ -116,6 +116,11 @@ namespace tools return invoke_http_json_rpc_update_is_disconnect("submitblock", req, rsp); } //------------------------------------------------------------------------------------------------------------------------------ + bool default_http_core_proxy::call_COMMAND_RPC_SUBMITBLOCK2(const currency::COMMAND_RPC_SUBMITBLOCK2::request& req, currency::COMMAND_RPC_SUBMITBLOCK2::response& rsp) + { + return invoke_http_json_rpc_update_is_disconnect("submitblock2", req, rsp); + } + //------------------------------------------------------------------------------------------------------------------------------ bool default_http_core_proxy::check_connection() { CRITICAL_REGION_LOCAL(m_lock); diff --git a/src/wallet/core_default_rpc_proxy.h b/src/wallet/core_default_rpc_proxy.h index ec43ab5f..b514adbc 100644 --- a/src/wallet/core_default_rpc_proxy.h +++ b/src/wallet/core_default_rpc_proxy.h @@ -39,6 +39,7 @@ namespace tools bool call_COMMAND_RPC_SCAN_POS(const currency::COMMAND_RPC_SCAN_POS::request& req, currency::COMMAND_RPC_SCAN_POS::response& rsp) override; bool call_COMMAND_RPC_GETBLOCKTEMPLATE(const currency::COMMAND_RPC_GETBLOCKTEMPLATE::request& req, currency::COMMAND_RPC_GETBLOCKTEMPLATE::response& rsp) override; bool call_COMMAND_RPC_SUBMITBLOCK(const currency::COMMAND_RPC_SUBMITBLOCK::request& req, currency::COMMAND_RPC_SUBMITBLOCK::response& rsp) override; + bool call_COMMAND_RPC_SUBMITBLOCK2(const currency::COMMAND_RPC_SUBMITBLOCK2::request& req, currency::COMMAND_RPC_SUBMITBLOCK2::response& rsp) override; bool call_COMMAND_RPC_GET_POS_MINING_DETAILS(const currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response& rsp) override; bool call_COMMAND_RPC_GET_BLOCKS_DETAILS(const currency::COMMAND_RPC_GET_BLOCKS_DETAILS::request& req, currency::COMMAND_RPC_GET_BLOCKS_DETAILS::response& res) override; bool call_COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN(const currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request& req, currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response& res) override; diff --git a/src/wallet/core_rpc_proxy.h b/src/wallet/core_rpc_proxy.h index 6688e75a..828bd509 100644 --- a/src/wallet/core_rpc_proxy.h +++ b/src/wallet/core_rpc_proxy.h @@ -36,6 +36,7 @@ namespace tools virtual bool call_COMMAND_RPC_SCAN_POS(const currency::COMMAND_RPC_SCAN_POS::request& req, currency::COMMAND_RPC_SCAN_POS::response& rsp){ return false; } virtual bool call_COMMAND_RPC_GETBLOCKTEMPLATE(const currency::COMMAND_RPC_GETBLOCKTEMPLATE::request& req, currency::COMMAND_RPC_GETBLOCKTEMPLATE::response& rsp){ return false; } virtual bool call_COMMAND_RPC_SUBMITBLOCK(const currency::COMMAND_RPC_SUBMITBLOCK::request& req, currency::COMMAND_RPC_SUBMITBLOCK::response& rsp){ return false; } + virtual bool call_COMMAND_RPC_SUBMITBLOCK2(const currency::COMMAND_RPC_SUBMITBLOCK2::request& req, currency::COMMAND_RPC_SUBMITBLOCK2::response& rsp) { return false; } virtual bool call_COMMAND_RPC_GET_POS_MINING_DETAILS(const currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response& rsp){ return false; } virtual bool call_COMMAND_RPC_GET_BLOCKS_DETAILS(const currency::COMMAND_RPC_GET_BLOCKS_DETAILS::request& req, currency::COMMAND_RPC_GET_BLOCKS_DETAILS::response& res){ return false; } virtual bool call_COMMAND_RPC_GET_OFFERS_EX(const currency::COMMAND_RPC_GET_OFFERS_EX::request& req, currency::COMMAND_RPC_GET_OFFERS_EX::response& res){ return false; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6d8fd766..cd17eb08 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -147,6 +147,11 @@ bool wallet2::set_core_proxy(const std::shared_ptr& proxy) return true; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_pos_mint_packing_size(uint64_t new_size) +{ + m_pos_mint_packing_size = new_size; +} +//---------------------------------------------------------------------------------------------------- std::shared_ptr wallet2::get_core_proxy() { return m_core_proxy; @@ -2295,22 +2300,22 @@ bool wallet2::generate_packing_transaction_if_needed(currency::transaction& tx, { prepare_free_transfers_cache(0); auto it = m_found_free_amounts.find(CURRENCY_BLOCK_REWARD); - if (it == m_found_free_amounts.end() || it->second.size() < WALLET_POS_MINT_PACKING_SIZE) + if (it == m_found_free_amounts.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 < WALLET_POS_MINT_PACKING_SIZE; it_ind++) + 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 < WALLET_POS_MINT_PACKING_SIZE) + 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 = WALLET_POS_MINT_PACKING_SIZE; + de.amount = m_pos_mint_packing_size*CURRENCY_BLOCK_REWARD; ctp.dsts.push_back(de); ctp.perform_packing = true; @@ -2835,10 +2840,13 @@ bool wallet2::build_minted_block(const currency::COMMAND_RPC_SCAN_POS::request& WLT_LOG_GREEN("Block constructed <" << get_block_hash(b) << ">, sending to core...", LOG_LEVEL_0); - currency::COMMAND_RPC_SUBMITBLOCK::request subm_req = AUTO_VAL_INIT(subm_req); - currency::COMMAND_RPC_SUBMITBLOCK::response subm_rsp = AUTO_VAL_INIT(subm_rsp); - subm_req.push_back(epee::string_tools::buff_to_hex_nodelimer(t_serializable_object_to_blob(b))); - m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK(subm_req, subm_rsp); + currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); + currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); + subm_req.b = t_serializable_object_to_blob(b); + if (tmpl_req.explicit_transaction.size()) + subm_req.explicit_txs.push_back(hexemizer{ tmpl_req.explicit_transaction }); + + m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK2(subm_req, subm_rsp); if (subm_rsp.status != CORE_RPC_STATUS_OK) { WLT_LOG_ERROR("Constructed block is not accepted by core, status: " << subm_rsp.status); @@ -3462,21 +3470,24 @@ bool wallet2::prepare_tx_sources_for_packing(uint64_t items_to_pack, size_t fake { prepare_free_transfers_cache(fake_outputs_count); auto it = m_found_free_amounts.find(CURRENCY_BLOCK_REWARD); - if (it == m_found_free_amounts.end() || it->second.size() < WALLET_POS_MINT_PACKING_SIZE) + if (it == m_found_free_amounts.end() || it->second.size() < m_pos_mint_packing_size) return false; - for (auto set_it = it->second.begin(); set_it != it->second.end(); it++) + for (auto set_it = it->second.begin(); set_it != it->second.end() && selected_indicies.size() <= m_pos_mint_packing_size; ) { 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++); } - it->second.erase(it->second.begin()); - if (!it->second.size()) - m_found_free_amounts.erase(it); + else + set_it++; } + if (!it->second.size()) + m_found_free_amounts.erase(it); return prepare_tx_sources(fake_outputs_count, sources, selected_indicies, found_money); } @@ -4128,10 +4139,10 @@ void wallet2::prepare_transaction(const construct_tx_param& ctp, finalize_tx_par uint64_t found_money = 0; TIME_MEASURE_START_MS(prepare_tx_sources_time); - if (ctp.multisig_id == currency::null_hash) + if (ctp.perform_packing) + prepare_tx_sources_for_packing(WALLET_DEFAULT_POS_MINT_PACKING_SIZE, 0, ftp.sources, ftp.selected_transfers, found_money); + else if (ctp.multisig_id == currency::null_hash) prepare_tx_sources(needed_money, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers, found_money); - else if (ctp.perform_packing) - prepare_tx_sources_for_packing(WALLET_POS_MINT_PACKING_SIZE, 0, ftp.sources, ftp.selected_transfers, found_money); else prepare_tx_sources(ctp.multisig_id, ftp.sources, found_money); TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3035904a..74c5e00f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -42,7 +42,7 @@ #define WALLET_DEFAULT_TX_SPENDABLE_AGE 10 #define WALLET_POS_MINT_CHECK_HEIGHT_INTERVAL 1 -#define WALLET_POS_MINT_PACKING_SIZE 100 +#define WALLET_DEFAULT_POS_MINT_PACKING_SIZE 100 #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" @@ -316,7 +316,8 @@ namespace tools m_last_sync_percent(0), m_do_rise_transfer(false), m_watch_only(false), - m_last_pow_block_h(0) + m_last_pow_block_h(0), + m_pos_mint_packing_size(WALLET_DEFAULT_POS_MINT_PACKING_SIZE) {}; public: wallet2() : m_stop(false), @@ -329,7 +330,8 @@ namespace tools m_do_rise_transfer(false), m_log_prefix("???"), m_watch_only(false), - m_last_pow_block_h(0) + m_last_pow_block_h(0), + m_pos_mint_packing_size(WALLET_DEFAULT_POS_MINT_PACKING_SIZE) { m_core_runtime_config = currency::get_default_core_runtime_config(); }; @@ -494,6 +496,7 @@ namespace tools bool set_core_proxy(const std::shared_ptr& proxy); + void set_pos_mint_packing_size(uint64_t new_size); std::shared_ptr get_core_proxy(); uint64_t balance() const; uint64_t balance(uint64_t& unloked, uint64_t& awaiting_in, uint64_t& awaiting_out, uint64_t& mined) const; @@ -867,6 +870,7 @@ private: std::atomic m_local_bc_height; //temporary workaround std::atomic m_last_bc_timestamp; bool m_do_rise_transfer; + uint64_t m_pos_mint_packing_size; transfer_container m_transfers; multisig_transfer_container m_multisig_transfers; diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index dc4ec806..27bb39f9 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -856,6 +856,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(packing_outputs_on_pos_minting_wallet); GENERATE_AND_PLAY(wallet_rpc_integrated_address); GENERATE_AND_PLAY(wallet_rpc_integrated_address_transfer); diff --git a/tests/core_tests/wallet_packing_tx.cpp b/tests/core_tests/wallet_packing_tx.cpp new file mode 100644 index 00000000..031b56cf --- /dev/null +++ b/tests/core_tests/wallet_packing_tx.cpp @@ -0,0 +1,571 @@ +// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2018 The Louisdor Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "chaingen.h" +#include "wallet_tests.h" +#include "wallet_test_core_proxy.h" +#include "../../src/wallet/wallet_public_structs_defs.h" +#include "offers_helper.h" +#include "string_coding.h" +#include "random_helper.h" +#include "tx_builder.h" + +using namespace epee; +using namespace crypto; +using namespace currency; + +const uint64_t uint64_max = std::numeric_limits::max(); +const std::wstring g_wallet_filename = L"~coretests.wallet.file.tmp"; +const std::string g_wallet_password = "dofatibmzibeziyekigo"; +const currency::account_base null_account = AUTO_VAL_INIT(null_account); + + +POD_MAKE_COMPARABLE(currency, tx_out); + +// Determines which output is real and actually spent in tx inputs, when there are fake outputs. +bool determine_tx_real_inputs(currency::core& c, const currency::transaction& tx, const currency::account_keys& keys, std::vector& real_inputs) +{ + struct local_visitor + { + local_visitor(const currency::account_keys& keys, const crypto::key_image key_image) + : m_keys(keys) + , m_txin_key_image(key_image) + , m_output_in_input_index(0) + , m_found(false) + {} + + bool handle_output(const transaction& source_tx, const transaction& validated_tx, const tx_out& out, uint64_t out_i) + { + CHECK_AND_ASSERT_MES(!m_found, false, "Internal error: m_found is true but the visitor is still being applied"); + auto it = std::find(validated_tx.vout.begin(), validated_tx.vout.end(), out); + if (it == validated_tx.vout.end()) + return false; + size_t output_tx_index = it - validated_tx.vout.begin(); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(validated_tx); + crypto::key_derivation derivation; + bool r = generate_key_derivation(tx_pub_key, m_keys.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed"); + crypto::secret_key ephemeral_secret_key; + derive_secret_key(derivation, output_tx_index, m_keys.m_spend_secret_key, ephemeral_secret_key); + + crypto::public_key output_public_key = boost::get(out.target).key; + + /*crypto::public_key ephemeral_public_key; + derive_public_key(derivation, output_tx_index, m_keys.m_account_address.m_spend_public_key, ephemeral_public_key);*/ + + crypto::key_image ki; + generate_key_image(output_public_key, ephemeral_secret_key, ki); + + if (ki == m_txin_key_image) + { + m_found = true; + return false; // to break the loop in scan_outputkeys_for_indexes + } + + ++m_output_in_input_index; + return true; + } + + currency::account_keys m_keys; + crypto::key_image m_txin_key_image; + size_t m_output_in_input_index; + bool m_found; + }; + + for (auto& txin : tx.vin) + { + const txin_to_key& in = boost::get(txin); + if (in.key_offsets.size() == 1) + { + real_inputs.push_back(0); // trivial case when no mixin is used + continue; + } + local_visitor vis(keys, in.k_image); + bool r = c.get_blockchain_storage().scan_outputkeys_for_indexes(tx, in, vis); + CHECK_AND_ASSERT_MES(r || vis.m_found, false, "scan_outputkeys_for_indexes failed"); + if (!vis.m_found) + return false; + real_inputs.push_back(vis.m_output_in_input_index); + } + + return true; +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +mined_balance_wallet_test::mined_balance_wallet_test() +{ + REGISTER_CALLBACK_METHOD(mined_balance_wallet_test, c1); + REGISTER_CALLBACK_METHOD(mined_balance_wallet_test, set_core_config); +} + +bool mined_balance_wallet_test::generate(std::vector& events) const +{ + GENERATE_ACCOUNT(preminer_acc); + GENERATE_ACCOUNT(miner_acc); + m_accounts.push_back(miner_acc); + GENERATE_ACCOUNT(alice_acc); + m_accounts.push_back(alice_acc); + + block blk_0 = AUTO_VAL_INIT(blk_0); + generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); + events.push_back(blk_0); + + DO_CALLBACK(events, "set_core_config"); + DO_CALLBACK(events, "c1"); + + return true; +} + +bool mined_balance_wallet_test::set_core_config(currency::core& c, size_t ev_index, const std::vector& events) +{ + core_runtime_config crc = c.get_blockchain_storage().get_core_runtime_config(); + crc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + crc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE; + c.get_blockchain_storage().set_core_runtime_config(crc); + return true; +} + +bool mined_balance_wallet_test::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + blockchain_storage& bcs = c.get_blockchain_storage(); + + core_runtime_config crc = bcs.get_core_runtime_config(); + crc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + bcs.set_core_runtime_config(crc); + + 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); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner", 0), false, "wrong balance"); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "alice", 0), false, "wrong balance"); + + uint64_t miner_mined_money = 0; + bool r = false; + std::list blocks; + + size_t n = CURRENCY_MINED_MONEY_UNLOCK_WINDOW; + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, n); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + r = bcs.get_blocks(bcs.get_current_blockchain_size() - n, n, blocks); + CHECK_AND_ASSERT_MES(r, false, "get_blocks failed"); + + for (auto& b : blocks) + miner_mined_money += get_outs_money_amount(b.miner_tx); + + miner_wlt->refresh(); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner", miner_mined_money, miner_mined_money), false, "wrong balance"); + + n = bcs.get_current_blockchain_size(); + r = miner_wlt->try_mint_pos(); + CHECK_AND_ASSERT_MES(r && bcs.get_current_blockchain_size() > n, false, "can't mint a PoS block"); + + block b = AUTO_VAL_INIT(b); + r = bcs.get_top_block(b); + CHECK_AND_ASSERT_MES(r, false, "get_top_block failed"); + CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 2, false, "Invalid PoS coinbase tx"); + + uint64_t coinbase_outs_amount = get_outs_money_amount(b.miner_tx); + uint64_t stake_amount = boost::get(b.miner_tx.vin[1]).amount; + CHECK_AND_ASSERT_MES(coinbase_outs_amount > stake_amount, false, "coinbase_outs_amount = " << coinbase_outs_amount << ", stake_amount = " << stake_amount << " : invalid condition"); + + miner_mined_money += coinbase_outs_amount - stake_amount; + + miner_wlt->refresh(); + + std::stringstream ss; + miner_wlt->dump_trunsfers(ss, false); + LOG_PRINT_CYAN("miner transfers: " << ENDL << ss.str(), LOG_LEVEL_0); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*miner_wlt.get(), "miner", miner_mined_money, miner_mined_money), false, "wrong balance"); + + return true; +} + +//------------------------------------------------------------------------------ + +wallet_outputs_with_same_key_image::wallet_outputs_with_same_key_image() +{ + REGISTER_CALLBACK_METHOD(wallet_outputs_with_same_key_image, c1); +} + +bool wallet_outputs_with_same_key_image::generate(std::vector& events) const +{ + // Test idea: make sure wallet does not take into account valid outputs having the same key image + // Only one such output is spendable thus only one output should be taken into account. + + bool r = false; + 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(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); + + uint64_t tx_amount = MK_TEST_COINS(3); + + // tx_1 + std::vector sources_1; + r = fill_tx_sources(sources_1, events, blk_0r, miner_acc.get_keys(), tx_amount + TESTS_DEFAULT_FEE, 0); + CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); + + std::vector destinations{ tx_destination_entry(tx_amount, alice_acc.get_public_address()) }; + + tx_builder builder; + builder.step1_init(); + builder.step2_fill_inputs(miner_acc.get_keys(), sources_1); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources_1); + + transaction tx_1 = builder.m_tx; + events.push_back(tx_1); + + // tx_2 with the same secret key + currency::keypair tmp_sec_key = builder.m_tx_key; + builder.step1_init(); + builder.m_tx_key = tmp_sec_key; + builder.m_tx.extra.clear(); + add_tx_pub_key_to_extra(builder.m_tx, builder.m_tx_key.pub); + + std::vector sources_2; + r = fill_tx_sources(sources_2, events, blk_0r, miner_acc.get_keys(), tx_amount + TESTS_DEFAULT_FEE, 0, sources_1); + CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); + + // keep destinations the same + + builder.step2_fill_inputs(miner_acc.get_keys(), sources_2); + builder.step3_fill_outputs(destinations); + builder.step4_calc_hash(); + builder.step5_sign(sources_2); + + transaction tx_2 = builder.m_tx; + events.push_back(tx_2); + + // make sure tx_1 and tx_2 have been created with the same tx key + CHECK_AND_ASSERT_MES(get_tx_pub_key_from_extra(tx_1) == get_tx_pub_key_from_extra(tx_2), false, "internal error: tx_1 and tx_2 have different pub keys"); + + // now both txs are in the pool, make sure they are + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_outputs_with_same_key_image::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + + // check Alice has no unlocked coins + bool r = refresh_wallet_and_check_balance("before tx_1 and tx_2 added", "Alice", alice_wlt, MK_TEST_COINS(3) * 2, true, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2, 0); + CHECK_AND_ASSERT_MES(r, false, ""); + + r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "there are txs in the pool!"); + + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + // only one tx_1 output is counted as the tx_2 output has the very same key image + r = refresh_wallet_and_check_balance("after tx_1 and tx_2 added", "Alice", alice_wlt, MK_TEST_COINS(3), true, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, MK_TEST_COINS(3)); + CHECK_AND_ASSERT_MES(r, false, ""); + + // make sure Alice is able to transfer her coins to smbd + std::vector destinations{ tx_destination_entry(MK_TEST_COINS(3) - TESTS_DEFAULT_FEE, m_accounts[MINER_ACC_IDX].get_public_address()) }; + try + { + alice_wlt->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + } + catch (...) + { + CHECK_AND_ASSERT_MES(false, false, "Alice failed to transfer all her funds"); + } + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "wrong tx count in the pool: " << c.get_pool_transactions_count()); + r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "there are txs in the pool!"); + + return true; +} + +//------------------------------------------------------------------------------ + +wallet_unconfirmed_tx_expiration::wallet_unconfirmed_tx_expiration() +{ + REGISTER_CALLBACK_METHOD(wallet_unconfirmed_tx_expiration, c1); +} + +bool wallet_unconfirmed_tx_expiration::generate(std::vector& events) const +{ + // Test outline: + // 1. Alice sends tx with expiration. + // 2. Miner ignores Alice's tx, so tx expires in the pool. + // 3. Tx is being removed from the pool due to expiration. + // Make sure Alice eventually spent no coins and all her money is unlocked in the wallet. + + 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(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); + + bool r = false; + transaction tx_0 = AUTO_VAL_INIT(tx_0); + r = construct_tx_with_many_outputs(events, blk_0r, miner_acc.get_keys(), alice_acc.get_public_address(), TESTS_DEFAULT_FEE * 20, 10, TESTS_DEFAULT_FEE, tx_0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs"); + events.push_back(tx_0); + transaction tx_1 = AUTO_VAL_INIT(tx_1); + r = construct_tx_with_many_outputs(events, blk_0r, miner_acc.get_keys(), bob_acc.get_public_address(), TESTS_DEFAULT_FEE * 20, 10, TESTS_DEFAULT_FEE, tx_1); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs"); + events.push_back(tx_1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({ tx_0, tx_1 })); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_unconfirmed_tx_expiration::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, ALICE_ACC_IDX); + + uint64_t alice_start_balance = TESTS_DEFAULT_FEE * 20; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, alice_start_balance), false, ""); + + // Alice constructs and sends tx with expiration time + uint64_t expiration_time = c.get_blockchain_storage().get_tx_expiration_median() + TX_EXPIRATION_MEDIAN_SHIFT + 15; + etc_tx_details_expiration_time extra_entry = AUTO_VAL_INIT(extra_entry); + extra_entry.v = expiration_time; + std::vector extra({ extra_entry }); // extra with expiration time + std::vector destinations({ tx_destination_entry(TESTS_DEFAULT_FEE * 2, m_accounts[MINER_ACC_IDX].get_public_address()) }); + transaction tx = AUTO_VAL_INIT(tx); + try + { + alice_wlt->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + } + catch (std::exception &e) + { + CHECK_AND_ASSERT_MES(false, false, "alice_wlt->transfer() caused an exception: " << e.what()); + } + + CHECK_AND_ASSERT_MES(get_tx_expiration_time(tx) == expiration_time, false, "tx expiration time wasn't set"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Invalid txs count in the pool: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("tx sent,", "Alice", alice_wlt, alice_start_balance - TESTS_DEFAULT_FEE * 2 - TESTS_DEFAULT_FEE, true, 0, UINT64_MAX, 0, 0, TESTS_DEFAULT_FEE * 2), false, ""); + + // mine a few block with no tx, so Alice's tx is expired in the pool + for (size_t i = 0; i < 5; ++i) + { + r = mine_next_pow_block_in_playtime_with_given_txs(m_accounts[MINER_ACC_IDX].get_public_address(), c, std::vector()); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime_with_given_txs failed"); + } + + // tx is still there + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Invalid txs count in the pool: " << c.get_pool_transactions_count()); + + // make sure expiration median was shifted enough + CHECK_AND_ASSERT_MES(c.get_blockchain_storage().is_tx_expired(tx), false, "wrong expiration time condition"); + + LOG_PRINT_CYAN("%%%%% tx_pool::on_idle()", LOG_LEVEL_0); + c.get_tx_pool().on_idle(); + + // make sure tx was removed by the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Invalid txs count in the pool: " << c.get_pool_transactions_count()); + + // mine one more block to trigger wallet's on_idle() and outdated tx clearing + r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + + // make sure all Alice's money are unlocked and no coins were actually spent + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("tx expired and removed from the pool,", "Alice", alice_wlt, alice_start_balance, true, 6, alice_start_balance, 0, 0, 0), false, ""); + + return true; +} + +//------------------------------------------------------------------------------ + +wallet_chain_switch_with_spending_the_same_ki::wallet_chain_switch_with_spending_the_same_ki() +{ + REGISTER_CALLBACK_METHOD(wallet_chain_switch_with_spending_the_same_ki, c1); +} + +bool wallet_chain_switch_with_spending_the_same_ki::generate(std::vector& events) const +{ + // Test outline + // 1. A wallet has one unspent output + // 2. wallet2::transfer() creates tx_0 that spends wallet's output + // 3. tx_0 is successfully put into the blockchain + // 4. Due to chain switch tx_0 is removed from the blockchain and get into the transaction pool + // 5. Make sure the wallet can't spend that output + // 6. After tx is expired make sure the wallet can spend that output + + + 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(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + MAKE_TX(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(30), blk_0r); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); + + // rewind blocks to allow wallet be able to spend the coins + REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_chain_switch_with_spending_the_same_ki::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, ALICE_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(30), true, UINT64_MAX, MK_TEST_COINS(30)), false, ""); + + std::vector destinations { tx_destination_entry(MK_TEST_COINS(30) - TESTS_DEFAULT_FEE, m_accounts[BOB_ACC_IDX].get_public_address()) }; + try + { + // create tx_1 + alice_wlt->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + } + catch (std::exception &e) + { + CHECK_AND_ASSERT_MES(false, false, "alice_wlt->transfer() caused an exception: " << e.what()); + } + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Tx pool has incorrect number of txs: " << c.get_pool_transactions_count()); + + // mine blk_2 on height 22 + CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, ""); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Tx pool has incorrect number of txs: " << c.get_pool_transactions_count()); + + // refresh wallet + alice_wlt->refresh(); + // DO NOT scan_tx_pool here intentionally + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(0)), false, ""); + + uint64_t blk_1r_height = c.get_top_block_height() - 1; + crypto::hash blk_1r_id = c.get_block_id_by_height(blk_1r_height); + block blk_2a = AUTO_VAL_INIT(blk_2a); + r = mine_next_pow_block_in_playtime_with_given_txs(m_accounts[MINER_ACC_IDX].get_public_address(), c, std::vector(), blk_1r_id, blk_1r_height + 1, &blk_2a); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime_with_given_txs failed"); + + // one more to trigger chain switch + block blk_3a = AUTO_VAL_INIT(blk_3a); + r = mine_next_pow_block_in_playtime_with_given_txs(m_accounts[MINER_ACC_IDX].get_public_address(), c, std::vector(), get_block_hash(blk_2a), get_block_height(blk_2a) + 1, &blk_3a); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime_with_given_txs failed"); + + // make sure tx_1 has been moved back to the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + CHECK_AND_ASSERT_MES(c.get_alternative_blocks_count() == 1, false, "Incorrect alt blocks count: " << c.get_alternative_blocks_count()); + + //const transaction& tx_1 = boost::get(events[4 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3]); + + // refresh wallet + alice_wlt->refresh(); + // DO NOT scan_tx_pool here intentionally + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(0)), false, ""); + + return true; +} + +//------------------------------------------------------------------------------ + +wallet_unconfimed_tx_balance::wallet_unconfimed_tx_balance() +{ + REGISTER_CALLBACK_METHOD(wallet_unconfimed_tx_balance, c1); +} + +bool wallet_unconfimed_tx_balance::generate(std::vector& events) const +{ + // Test outline: + // 1. Miner sends 100 coins to Alice (50 + 50) + // 2. Alice sends 30 back to Miner (tx is unconfirmed) + // 3. Make sure Alice's wallet has correct balance, when it is checked from wallet's callback + // 4. Few blocks are mined so the tx is get confirmed + // 5. Make sure Alice's balance has changed correctly + + 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(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + MAKE_TX(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(50), blk_0r); + MAKE_TX(events, tx_1, miner_acc, alice_acc, MK_TEST_COINS(50), blk_0r); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({ tx_0, tx_1 })); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_unconfimed_tx_balance::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, ALICE_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(100), false, UINT64_MAX, MK_TEST_COINS(100)), false, ""); + + bool callback_is_ok = false; + // this callback will ba called from within wallet2::transfer() below + std::shared_ptr l(new wlt_lambda_on_transfer2_wrapper( + [&callback_is_ok](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool + { + CHECK_AND_ASSERT_MES(balance == MK_TEST_COINS(70), false, "invalid balance: " << print_money_brief(balance)); + CHECK_AND_ASSERT_MES(unlocked_balance == MK_TEST_COINS(50), false, "invalid unlocked_balance: " << print_money_brief(unlocked_balance)); + CHECK_AND_ASSERT_MES(total_mined == 0, false, "invalid total_mined: " << print_money_brief(total_mined)); + callback_is_ok = true; + return true; + } + )); + alice_wlt->callback(l); + + uint64_t fee = TESTS_DEFAULT_FEE * 3; + std::vector destinations{ tx_destination_entry(MK_TEST_COINS(30) - fee, m_accounts[MINER_ACC_IDX].get_public_address()) }; + try + { + alice_wlt->transfer(destinations, 0, 0, fee, empty_extra, empty_attachment); + } + catch (std::exception &e) + { + CHECK_AND_ASSERT_MES(false, false, "alice_wlt->transfer() caused an exception: " << e.what()); + } + + CHECK_AND_NO_ASSERT_MES(callback_is_ok, false, "callback failed"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Tx pool has incorrect number of txs: " << c.get_pool_transactions_count()); + + // 50 coins should be locked and 50 - unlocked + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(70), false, UINT64_MAX, MK_TEST_COINS(50), 0, 0, MK_TEST_COINS(30) - fee), false, ""); + + // mine WALLET_DEFAULT_TX_SPENDABLE_AGE blocks so the tx get confirmed and coins get unlocked + CHECK_AND_ASSERT_MES(mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Tx pool has incorrect number of txs: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(70), false, UINT64_MAX, MK_TEST_COINS(70), 0, 0, 0), false, ""); + + return true; +} diff --git a/tests/core_tests/wallet_packing_tx.h b/tests/core_tests/wallet_packing_tx.h new file mode 100644 index 00000000..8e2fb4a6 --- /dev/null +++ b/tests/core_tests/wallet_packing_tx.h @@ -0,0 +1,17 @@ +// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2018 The Louisdor Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include "chaingen.h" +#include "wallet_tests_basic.h" + +struct packing_for_pos_minting_wallet_test : public wallet_test +{ + packing_for_pos_minting_wallet_test(); + mined_balance_wallet_test(); + bool generate(std::vector& events) const; + bool set_core_config(currency::core& c, size_t ev_index, const std::vector& events); + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; \ No newline at end of file diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 05dd6f19..870f3099 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3297,3 +3297,79 @@ bool wallet_unconfimed_tx_balance::c1(currency::core& c, size_t ev_index, const return true; } + +//------------------------------------------------------------------------------ + +packing_outputs_on_pos_minting_wallet::packing_outputs_on_pos_minting_wallet() +{ + REGISTER_CALLBACK_METHOD(packing_outputs_on_pos_minting_wallet, c1); + REGISTER_CALLBACK_METHOD(packing_outputs_on_pos_minting_wallet, set_core_config); +} +bool packing_outputs_on_pos_minting_wallet::generate(std::vector& events) const +{ + + // 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); + + // don't use MAKE_GENESIS_BLOCK here because it will mask 'generator' + currency::block blk_0 = AUTO_VAL_INIT(blk_0); + generator.construct_genesis_block(blk_0, miner_acc, test_core_time::get_time()); + events.push_back(blk_0); + + DO_CALLBACK(events, "set_core_config"); + + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW+5); + + //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); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool packing_outputs_on_pos_minting_wallet::set_core_config(currency::core& c, size_t ev_index, const std::vector& events) +{ + core_runtime_config crc = c.get_blockchain_storage().get_core_runtime_config(); + crc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + crc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE; + c.get_blockchain_storage().set_core_runtime_config(crc); + return true; +} + +bool packing_outputs_on_pos_minting_wallet::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_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"); + + miner_wlt->set_pos_mint_packing_size(4); + check_balance_via_wallet(*miner_wlt.get(), "miner_wlt", MK_TEST_COINS(2000), 0, MK_TEST_COINS(2000), 0, 0); + + miner_wlt->try_mint_pos(); + + 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"); + + 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_balance_via_wallet(*miner_wlt.get(), "miner_wlt", uint64_max, MK_TEST_COINS(2000) + top_block_reward, 0, 0, 0); + + miner_wlt->reset_password(g_wallet_password); + miner_wlt->store(g_wallet_filename); + + return true; +} \ No newline at end of file diff --git a/tests/core_tests/wallet_tests.h b/tests/core_tests/wallet_tests.h index 6fbd37bd..89dfb05f 100644 --- a/tests/core_tests/wallet_tests.h +++ b/tests/core_tests/wallet_tests.h @@ -251,3 +251,11 @@ struct wallet_unconfimed_tx_balance : public wallet_test bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); }; + +struct packing_outputs_on_pos_minting_wallet : public wallet_test +{ + packing_outputs_on_pos_minting_wallet(); + bool generate(std::vector& events) const; + bool set_core_config(currency::core& c, size_t ev_index, const std::vector& events); + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +};