diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 1c33fbcbaf7..07293041191 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -657,7 +657,7 @@ else() add_cxx_flag_if_supported(-Wformat-security CXX_SECURITY_FLAGS) # -fstack-protector - if (NOT WIN32) + if (NOT OPENBSD AND NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1))) add_c_flag_if_supported(-fstack-protector C_SECURITY_FLAGS) add_cxx_flag_if_supported(-fstack-protector CXX_SECURITY_FLAGS) add_c_flag_if_supported(-fstack-protector-strong C_SECURITY_FLAGS) @@ -665,9 +665,11 @@ else() endif() # New in GCC 8.2 - if (NOT WIN32) + if (NOT OPENBSD AND NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1))) add_c_flag_if_supported(-fcf-protection=full C_SECURITY_FLAGS) add_cxx_flag_if_supported(-fcf-protection=full CXX_SECURITY_FLAGS) + endif() + if (NOT WIN32 AND NOT OPENBSD) add_c_flag_if_supported(-fstack-clash-protection C_SECURITY_FLAGS) add_cxx_flag_if_supported(-fstack-clash-protection CXX_SECURITY_FLAGS) endif() @@ -679,8 +681,8 @@ else() endif() # linker - if (NOT WIN32) - # Windows binaries die on startup with PIE + if (NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1))) + # Windows binaries die on startup with PIE when compiled with GCC <9.x add_linker_flag_if_supported(-pie LD_SECURITY_FLAGS) endif() add_linker_flag_if_supported(-Wl,-z,relro LD_SECURITY_FLAGS) @@ -704,6 +706,7 @@ else() if (WIN32) add_linker_flag_if_supported(-Wl,--dynamicbase LD_SECURITY_FLAGS) add_linker_flag_if_supported(-Wl,--nxcompat LD_SECURITY_FLAGS) + add_linker_flag_if_supported(-Wl,--high-entropy-va LD_SECURITY_FLAGS) endif() message(STATUS "Using C security hardening flags: ${C_SECURITY_FLAGS}") diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index e7e971de892..46fb675ce86 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -46,6 +46,26 @@ using namespace epee; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "cn" +#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0]) + +const uint64_t BLOCK_PER_YEAR = 259200; + +const uint64_t FULL_STAKE_COINS_OVER_YEAR[13] = { + 300000, + 600000, + 900000, + 1350000, // 1.5 + 2025000, // 1.5 + 2632500, // 1.3 + 3422250, // 1.3 + 4448925, // 1.3 + 5338710, // 1.2 + 6406452, // 1.2 + 7687742, // 1.2 + 9225290, // 1.2 + 10000000, // 1.2, XNC_INT_MAX is 10000000 * COIN, so this will hardly happen +}; + namespace cryptonote { struct integrated_address { @@ -318,8 +338,64 @@ namespace cryptonote { } bool operator ==(const cryptonote::block& a, const cryptonote::block& b) { - return cryptonote::get_block_hash(a) == cryptonote::get_block_hash(b); + return cryptonote::get_block_hash(a) == cryptonote::get_block_hash(b); + } + //-------------------------------------------------------------------------------- + double get_pos_block_reward_rate(uint64_t unlock_time, uint64_t block_height, uint64_t block_time, uint64_t staked_coins, uint64_t cur_height, network_type type) + { + double reward_rate = 0.0; + + // at least staked 1 XMC + staked_coins /= COIN; + if (!staked_coins) + return reward_rate; + + uint64_t start_height = (type == network_type::TESTNET ? STAKE_STATR_HEIGHT_TESTNET : STAKE_START_HEIGHT); + + if (cur_height < start_height) + return reward_rate; + + uint64_t full_stake_coins = FULL_STAKE_COINS_OVER_YEAR[0]; // 300'000 XMC + uint64_t elapse_index = (cur_height - start_height) / BLOCK_PER_YEAR; + if (elapse_index >= ARRAY_SIZE(FULL_STAKE_COINS_OVER_YEAR)) + full_stake_coins = FULL_STAKE_COINS_OVER_YEAR[ARRAY_SIZE(FULL_STAKE_COINS_OVER_YEAR) - 1]; + else + full_stake_coins = FULL_STAKE_COINS_OVER_YEAR[elapse_index]; + + const uint64_t FULL_STAKE_TIME_DAYS = 12 * 30; // one year + + do + { + uint64_t delta_height = 0; + if (unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER){ + if (unlock_time < block_height) + break; + + delta_height = unlock_time - block_height; + } else { + if (unlock_time < block_time) + break; + + delta_height = (unlock_time - block_time) / DIFFICULTY_TARGET_V2; + } + + if (delta_height > BLOCK_PER_YEAR) + delta_height = BLOCK_PER_YEAR; + + // at least staked one day + uint64_t delta_days = delta_height / MONERO_BLOCK_PER_DAY; + if (!delta_days) + break; + + // This could make uint64_t overflow + //reward_rate = 1.0 * (staked_coins * delta_days * delta_days) / (full_stake_coins * FULL_STAKE_TIME_DAYS * FULL_STAKE_TIME_DAYS); + reward_rate = 1.0 * staked_coins / full_stake_coins * delta_days / FULL_STAKE_TIME_DAYS * delta_days / FULL_STAKE_TIME_DAYS; + + }while (0); + + return reward_rate; } + //-------------------------------------------------------------------------------- } //-------------------------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index c7198a16f3b..11efab7503a 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -91,6 +91,7 @@ namespace cryptonote { bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version); uint8_t get_account_address_checksum(const public_address_outer_blob& bl); uint8_t get_account_integrated_address_checksum(const public_integrated_address_outer_blob& bl); + double get_pos_block_reward_rate(uint64_t unlock_time, uint64_t block_height, uint64_t block_time, uint64_t staked_coins, uint64_t cur_height, network_type type); std::string get_account_address_as_str( network_type nettype diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 85ab27bcb0c..2487d9b0ed3 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -594,6 +594,7 @@ namespace cryptonote if (!pick(nar, tx_extra_fields, TX_EXTRA_MERGE_MINING_TAG)) return false; if (!pick(nar, tx_extra_fields, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG)) return false; if (!pick(nar, tx_extra_fields, TX_EXTRA_TAG_PADDING)) return false; + if (!pick(nar, tx_extra_fields, TX_EXTRA_TAG_STAKE)) return false; // if not empty, someone added a new type and did not add a case above if (!tx_extra_fields.empty()) @@ -769,6 +770,73 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + bool get_tx_stake_from_extra(crypto::public_key& spend_pub_key, crypto::secret_key& view_secret_key, std::vector& tx_ids, const std::vector& extra_stake) + { + const size_t HASH_SIZE = sizeof(crypto::hash); // public_key, secret_key, hash has same size + if (extra_stake.size() % HASH_SIZE != 0 || extra_stake.size() < HASH_SIZE * 3) + return false; + + std::copy(extra_stake.data(), extra_stake.data() + HASH_SIZE, spend_pub_key.data); + + std::copy(extra_stake.data() + HASH_SIZE, extra_stake.data() + 2 * HASH_SIZE, view_secret_key.data); + + size_t cnt = extra_stake.size() / HASH_SIZE - 2; + for (size_t i = 0; i < cnt; ++i) + { + crypto::hash id; + std::copy(extra_stake.data() + HASH_SIZE * (2 + i), extra_stake.data() + HASH_SIZE * (3 + i), id.data); + tx_ids.push_back(id); + } + return true; + } + //--------------------------------------------------------------- + bool get_tx_stake_from_extra(crypto::public_key& spend_pub_key, crypto::secret_key& view_secret_key, std::vector& tx_ids, const std::vector &tx_extra, size_t stk_index) + { + std::vector tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); + + tx_extra_stake stake_field; + if(!find_tx_extra_field_by_type(tx_extra_fields, stake_field, stk_index)) + return false; + + spend_pub_key = stake_field.spend_pub_key; + view_secret_key = stake_field.view_secret_key; + tx_ids = stake_field.tx_id; + + return true; + } + //--------------------------------------------------------------- + bool add_stake_to_extra(std::vector& tx_extra, const std::vector &extra_stake) + { + crypto::public_key spk = AUTO_VAL_INIT(spk); + crypto::secret_key vsk = AUTO_VAL_INIT(vsk); + std::vector ti = AUTO_VAL_INIT(ti); + + if (!get_tx_stake_from_extra(spk, vsk, ti, extra_stake)) + return false; + + // parse stake + tx_extra_stake stake; + stake.spend_pub_key = spk; + stake.view_secret_key = vsk; + stake.tx_id = ti; + stake.count = static_cast(ti.size()); + + // convert to variant + tx_extra_field field = stake; + // serialize + std::ostringstream oss; + binary_archive ar(oss); + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra stake"); + // append + std::string tx_extra_str = oss.str(); + size_t pos = tx_extra.size(); + tx_extra.resize(tx_extra.size() + tx_extra_str.size()); + memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size()); + return true; + } + //--------------------------------------------------------------- bool get_inputs_money_amount(const transaction& tx, uint64_t& money) { money = 0; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index f3cc62d99c1..fa3a77bab5e 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -85,6 +85,9 @@ namespace cryptonote void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); + bool add_stake_to_extra(std::vector& tx_extra, const std::vector& extra_stake); + bool get_tx_stake_from_extra(crypto::public_key& spend_pub_key, crypto::secret_key& view_secret_key, std::vector& tx_ids, const std::vector& extra_stake); + bool get_tx_stake_from_extra(crypto::public_key& spend_pub_key, crypto::secret_key& view_secret_key, std::vector& tx_id, const std::vector& tx_extra, size_t stk_index); bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector& additional_tx_public_keys, size_t output_index); struct subaddress_receive_info { diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index b6cfdf2c97a..456b9cb4fe8 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -35,8 +35,6 @@ #include #include "int-util.h" -#include "crypto/hash.h" -#include "cryptonote_config.h" #include "difficulty.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index d554e4f343a..a48b86313ed 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -36,6 +36,7 @@ #include #include "crypto/hash.h" +#include "cryptonote_config.h" namespace cryptonote { diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index e594eb0494c..a25eb0f48b8 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -43,6 +43,8 @@ #include "string_tools.h" #include "storages/portable_storage_template_helper.h" #include "boost/logic/tribool.hpp" +#include "rapidjson/document.h" +#include "common/json_util.h" #ifdef __APPLE__ #include @@ -75,6 +77,7 @@ #define AUTODETECT_WINDOW 10 // seconds #define AUTODETECT_GAIN_THRESHOLD 1.02f // 2% +#define POS_MAX_TX_COUNT 20 using namespace epee; @@ -96,6 +99,7 @@ namespace cryptonote const command_line::arg_descriptor arg_bg_mining_min_idle_interval_seconds = {"bg-mining-min-idle-interval", "Specify min lookback interval in seconds for determining idle state", miner::BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS, true}; const command_line::arg_descriptor arg_bg_mining_idle_threshold_percentage = {"bg-mining-idle-threshold", "Specify minimum avg idle percentage over lookback interval", miner::BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE, true}; const command_line::arg_descriptor arg_bg_mining_miner_target_percentage = {"bg-mining-miner-target", "Specify maximum percentage cpu use by miner(s)", miner::BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE, true}; + const command_line::arg_descriptor arg_pos_settings = {"pos-settings-file", "Specify file for pos settings", "", true}; } @@ -121,7 +125,8 @@ namespace cryptonote m_idle_threshold(BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE), m_mining_target(BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE), m_miner_extra_sleep(BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS), - m_block_reward(0) + m_block_reward(0), + m_modify_time(0) { m_attrs.set_stack_size(THREAD_STACK_SIZE); } @@ -165,7 +170,33 @@ namespace cryptonote extra_nonce = m_extra_messages[m_config.current_extra_message_index]; } - if(!m_phandler->get_block_template(bl, m_mine_address, di, height, expected_reward, extra_nonce)) + // check if pos settings file changed, if changed, we read new settings, if not, we leave it + time_t mt; + if (epee::file_io_utils::get_file_time(m_pos_settings_file, mt)) + { + if (m_modify_time != mt) + { + load_pos_settings(m_pos_settings_file); + m_modify_time = mt; + } + } + + std::vector extra_stake; + if (m_pos_settings.tx_id.size()) + { + extra_stake.reserve( sizeof(crypto::public_key) + sizeof(crypto::secret_key) + sizeof(crypto::hash) * m_pos_settings.tx_id.size()); + // copy spend public key + std::copy(&m_mine_address.m_spend_public_key.data[0], &m_mine_address.m_spend_public_key.data[sizeof(crypto::public_key)], std::back_inserter(extra_stake)); + // copy view secret key + std::copy(&m_pos_settings.view_secret_key.data[0], &m_pos_settings.view_secret_key.data[sizeof(crypto::secret_key)], std::back_inserter(extra_stake)); + // copy each tx id + for (const auto& id: m_pos_settings.tx_id) + { + std::copy(&id.data[0], &id.data[sizeof(crypto::hash)], std::back_inserter(extra_stake)); + } + } + + if(!m_phandler->get_block_template(bl, m_mine_address, di, height, expected_reward, extra_nonce, extra_stake)) { LOG_ERROR("Failed to get_block_template(), stopping mining"); return false; @@ -285,6 +316,7 @@ namespace cryptonote command_line::add_arg(desc, arg_bg_mining_min_idle_interval_seconds); command_line::add_arg(desc, arg_bg_mining_idle_threshold_percentage); command_line::add_arg(desc, arg_bg_mining_miner_target_percentage); + command_line::add_arg(desc, arg_pos_settings); } //----------------------------------------------------------------------------------------------------- bool miner::init(const boost::program_options::variables_map& vm, network_type nettype) @@ -330,6 +362,21 @@ namespace cryptonote } } + if(command_line::has_arg(vm, arg_pos_settings)) + { + CHECK_AND_ASSERT_MES(m_do_mining, false, "Must specify start-ming argument when using pos-settings-file"); + + std::string filename = command_line::get_arg(vm, arg_pos_settings); + load_pos_settings(filename); + m_pos_settings_file = filename; + + time_t mt; + if (epee::file_io_utils::get_file_time(filename, mt)) + { + m_modify_time = mt; + } + } + // Background mining parameters // Let init set all parameters even if background mining is not enabled, they can start later with params set if(command_line::has_arg(vm, arg_bg_mining_enable)) @@ -1129,4 +1176,62 @@ namespace cryptonote LOG_ERROR("couldn't query power status"); return boost::logic::tribool(boost::logic::indeterminate); } + + bool miner::load_pos_settings(const std::string& filename) + { + std::string buf; + bool r = epee::file_io_utils::is_file_exist(filename); + CHECK_AND_ASSERT_MES(r, false, "pos settings file not exist"); + + r = epee::file_io_utils::load_file_to_string(filename, buf); + CHECK_AND_ASSERT_MES(r, false, "Failed to load pos settings file " << filename); + + rapidjson::Document json; + r = json.Parse(buf.c_str()).HasParseError(); + CHECK_AND_ASSERT_MES(!r, false, "Failed to parse pos settings JSON file " << filename); + + // parse view_secret_key field + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, view_secret_key, std::string, String, false, std::string()); + CHECK_AND_ASSERT_MES(field_view_secret_key_found, false, "Failed to found view secret key"); + + cryptonote::blobdata viewkey_data; + r = epee::string_tools::parse_hexstr_to_binbuff(field_view_secret_key, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse view key secret key"); + + crypto::secret_key viewkey = *reinterpret_cast(viewkey_data.data()); + crypto::public_key pkey; + r = crypto::secret_key_to_public_key(viewkey, pkey); + CHECK_AND_ASSERT_MES(r, false, "Failed to verify view key secret key"); + CHECK_AND_ASSERT_MES(pkey == m_mine_address.m_view_public_key, false, "view secret key does not match mine address"); + + m_pos_settings = AUTO_VAL_INIT(m_pos_settings); + m_pos_settings.view_secret_key = viewkey; + + // parse tx_id field + r = json.HasMember("tx_id"); + CHECK_AND_ASSERT_MES(r, false, "Failed to found tx id"); + + const rapidjson::Value& array_txid = json["tx_id"]; + r = array_txid.IsArray(); + CHECK_AND_ASSERT_MES(r, false, "tx id must be array"); + + rapidjson::SizeType sz = array_txid.Size(); + CHECK_AND_ASSERT_MES(sz <= POS_MAX_TX_COUNT, false, "tx id should be less than " << POS_MAX_TX_COUNT); + for (rapidjson::SizeType i = 0; i < sz; i++) { + std::string field_id = static_cast(array_txid[i].GetString()); + cryptonote::blobdata id_data; + r = epee::string_tools::parse_hexstr_to_binbuff(field_id, id_data) || id_data.size() != sizeof(crypto::hash); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse tx id"); + + crypto::hash id = *reinterpret_cast(id_data.data()); + if (std::find(m_pos_settings.tx_id.begin(), m_pos_settings.tx_id.end(), id) != m_pos_settings.tx_id.end()) + continue; + m_pos_settings.tx_id.push_back(id); + } + + CHECK_AND_ASSERT_MES(m_pos_settings.tx_id.size(), false, "tx id can not be empty"); + MINFO("POS: Loaded view key succeed, and with " << m_pos_settings.tx_id.size() << " tx ids"); + + return true; + } } diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index ac7a0381c58..b9142dffb72 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -47,7 +47,7 @@ namespace cryptonote struct i_miner_handler { virtual bool handle_block_found(block& b, block_verification_context &bvc) = 0; - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0; + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake) = 0; protected: ~i_miner_handler(){}; }; @@ -106,6 +106,7 @@ namespace cryptonote bool request_block_template(); void merge_hr(); void update_autodetection(); + bool load_pos_settings(const std::string& filename); struct miner_config { @@ -116,6 +117,11 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; + struct pos_config + { + crypto::secret_key view_secret_key; + std::vector tx_id; + }; volatile uint32_t m_stop; epee::critical_section m_template_lock; @@ -173,5 +179,11 @@ namespace cryptonote static uint8_t get_percent_of_total(uint64_t some_time, uint64_t total_time); static boost::logic::tribool on_battery_power(); std::atomic m_block_reward; + + // pos mining stuffs .. + + pos_config m_pos_settings; + std::string m_pos_settings_file; + time_t m_modify_time; }; } diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index ecb4c604086..1d0ce9a2c6e 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -40,6 +40,7 @@ #define TX_EXTRA_MERGE_MINING_TAG 0x03 #define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04 #define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE +#define TX_EXTRA_TAG_STAKE 0x58 #define TX_EXTRA_NONCE_PAYMENT_ID 0x00 #define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01 @@ -179,11 +180,26 @@ namespace cryptonote END_SERIALIZE() }; + struct tx_extra_stake + { + crypto::public_key spend_pub_key; + crypto::secret_key view_secret_key; + uint8_t count; + std::vector tx_id; + + BEGIN_SERIALIZE() + FIELD(spend_pub_key) + FIELD(view_secret_key) + FIELD(count) + FIELD(tx_id) + END_SERIALIZE() + }; + // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: // varint tag; // varint size; // varint data[]; - typedef boost::variant tx_extra_field; + typedef boost::variant tx_extra_field; } VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); @@ -192,3 +208,4 @@ VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE); VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG); VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS); VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_stake, TX_EXTRA_TAG_STAKE); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index d3c079b4d37..c6b9db96ad6 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -101,6 +101,9 @@ #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 DIFFICULTY_TARGET_V2 * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 +#define STAKE_START_HEIGHT 1990600 // Dec 7, 2019 +#define STAKE_STATR_HEIGHT_TESTNET 4000 + #define DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN DIFFICULTY_TARGET_V1 //just alias; used by tests @@ -183,6 +186,7 @@ #define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved #define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved //#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED +#define MONERO_BLOCK_PER_DAY 720 // block per day // New constants are intended to go here namespace config diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index d3d02852d4a..ce5e0ebdd91 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -103,7 +103,7 @@ static const struct { { 4, 1220516, 0, 1483574400 }, // version 5 starts from block 1288616, which is on or around the 15th of April, 2017. Fork time finalised on 2017-03-14. - { 5, 1288616, 0, 1489520158 }, + { 5, 1288616, 0, 1489520158 }, // version 6 starts from block 1400000, which is on or around the 16th of September, 2017. Fork time finalised on 2017-08-18. { 6, 1400000, 0, 1503046577 }, @@ -1250,6 +1250,42 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height) bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version) { LOG_PRINT_L3("Blockchain::" << __func__); + + uint64_t height = boost::get(b.miner_tx.vin[0]).height; + double pos_reward_rate = 0.0; + crypto::public_key spk = AUTO_VAL_INIT(spk); + crypto::secret_key vsk = AUTO_VAL_INIT(vsk); + std::vector ti = AUTO_VAL_INIT(ti); + bool r = get_tx_stake_from_extra(spk, vsk, ti, b.miner_tx.extra, 0); + if (r) + { + // miner use pos, we should make check + // 1. check if vout and spk, vsk match + crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx.extra); + + account_keys acc; + acc.m_view_secret_key = vsk; + acc.m_account_address.m_spend_public_key = spk; + + r = false; + for (size_t i = 0; i < b.miner_tx.vout.size(); ++i) + { + const tx_out& tax_out = b.miner_tx.vout[i]; + std::vector additional_derivations; + r = cryptonote::is_out_to_acc(acc, boost::get(tax_out.target), tx_pub_key, additional_derivations, i); + // there are should be two outputs, one is miner reward, another is funding reward, we need miner reward be true should ok + if (r) break; + } + CHECK_AND_ASSERT_MES(r, false, "failed to validate miner's stake extra"); + + // 2. check if amount match pos's stake + crypto::public_key vpk = AUTO_VAL_INIT(vpk); + r = crypto::secret_key_to_public_key(vsk, vpk); + CHECK_AND_ASSERT_MES(r, false, "illegal view secret key in stake extra"); + + r = check_miner_stakes(spk, vsk, ti, height, pos_reward_rate); + } + //validate reward uint64_t money_in_use = 0; for (auto& o: b.miner_tx.vout) @@ -1270,9 +1306,9 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } } - uint64_t height = boost::get(b.miner_tx.vin[0]).height; uint64_t funding_amount = 0; uint64_t miner_reward_amount = 0; + // cryptonote::BlockFunding fundctl; // CHECK_AND_ASSERT_MES(fundctl.init(m_nettype), false, "init fundctl failed"); // if (fundctl.funding_enabled(height)) @@ -1293,6 +1329,9 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl MERROR_VER("block weight " << cumulative_block_weight << " is bigger than allowed for this blockchain"); return false; } + + uint64_t std_reward = base_reward; + if(base_reward + fee < money_in_use) { MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); @@ -1324,8 +1363,10 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl // bool ret = fundctl.check_block_funding(miner_reward_amount, funding_amount, base_reward + fee); uint64_t adjust_height = m_nettype == TESTNET ? DIFFICULTY_ADJUST_HEIGHT_TESTNET : DIFFICULTY_ADJUST_HEIGHT; bool fork = height >= adjust_height; - bool ret = m_fundctl.check_block_funding(miner_reward_amount, funding_amount, base_reward + fee, fork); - MINFO("miner_reward_amount=" << miner_reward_amount << ", funding_amount=" << funding_amount << ", money_in_use=" << (base_reward + fee)); + + bool ret = m_fundctl.check_block_funding(miner_reward_amount - fee, funding_amount, std_reward, pos_reward_rate, fork); + MINFO("miner_reward_amount=" << miner_reward_amount << ", funding_amount=" << funding_amount << ", money_in_use=" << (base_reward + fee) + << ", base_rewad=" << base_reward << ", fee=" << fee); CHECK_AND_ASSERT_MES(ret, false, "check reward failed"); } return true; @@ -1418,7 +1459,7 @@ uint64_t Blockchain::get_current_cumulative_block_weight_median() const // in a lot of places. That flag is not referenced in any of the code // nor any of the makefiles, howeve. Need to look into whether or not it's // necessary at all. -bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake) { LOG_PRINT_L3("Blockchain::" << __func__); size_t median_weight; @@ -1522,12 +1563,31 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); + // calculate pos reward + double pos_reward_rate = 0.0; + if (!ex_stake.empty()) + { + crypto::public_key spk = AUTO_VAL_INIT(spk); + crypto::secret_key vsk = AUTO_VAL_INIT(vsk); + std::vector ti = AUTO_VAL_INIT(ti); + bool r = get_tx_stake_from_extra(spk, vsk, ti, ex_stake); + CHECK_AND_ASSERT_MES(r, false, "failed to parse tx extra stake"); + CHECK_AND_ASSERT_MES(spk == miner_address.m_spend_public_key, false, "failed to construct stake extra spend pub key"); + + crypto::public_key pkey = AUTO_VAL_INIT(pkey); + r = crypto::secret_key_to_public_key(vsk, pkey); + CHECK_AND_ASSERT_MES(r, false, "failed to verify view key secret key"); + CHECK_AND_ASSERT_MES(pkey == miner_address.m_view_public_key, false, "view secret key does not match mine address"); + CHECK_AND_ASSERT_MES(check_miner_stakes(spk, vsk, ti, height, pos_reward_rate), false, "check miner's pos failed"); + } + size_t txs_weight; uint64_t fee; if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, b.major_version)) { return false; } + //expected_reward += pos_reward; pool_cookie = m_tx_pool.cookie(); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) size_t real_txs_weight = 0; @@ -1590,7 +1650,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, uint8_t hf_version = b.major_version; size_t max_outs = hf_version >= 4 ? 1 : 11; // bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); - bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version, m_nettype); + bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version, m_nettype, ex_stake, pos_reward_rate); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -1600,7 +1660,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, for (size_t try_count = 0; try_count != 10; ++try_count) { // r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); - r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version, m_nettype); + r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version, m_nettype, ex_stake, pos_reward_rate); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); size_t coinbase_weight = get_transaction_weight(b.miner_tx); @@ -1652,9 +1712,9 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, return false; } //------------------------------------------------------------------ -bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake) { - return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce); + return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce, ex_stake); } //------------------------------------------------------------------ // for an alternate chain, get the timestamps from the main chain to complete @@ -5082,6 +5142,108 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_ m_btc_valid = true; } +bool Blockchain::check_miner_stakes(const public_key &spend_pubkey, const crypto::secret_key& view_seckey, const std::vector &ti, uint64_t height, double &stake_reward_rate) +{ + stake_reward_rate = 0.0; + hw::device &hwd = hw::get_device("default"); + bool r = false; + + for(const auto& txid: ti) + { + cryptonote::blobdata bd; + if (!m_db->get_tx_blob(txid, bd)) + continue; + + transaction tx; + if (!parse_and_validate_tx_from_blob(bd, tx)) + continue; + + uint64_t amount = 0, tx_block_height = 0, tx_block_time = 0; + + // we only need locked tx, and since tx is locked, so it's unspent, thus we don't need check double spend. + if (is_tx_spendtime_unlocked(tx.unlock_time)) + continue; + + // we don't want coinbase tx + if (is_coinbase(tx)) + continue; + + tx_block_height = m_db->get_tx_block_height(txid); + if (!tx_block_height) + continue; + + tx_block_time = m_db->get_block_timestamp(tx_block_height); + if (!tx_block_time) + continue; + + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if (tx_pub_key == null_pkey) + continue; + + crypto::key_derivation derivation; + r = hwd.generate_key_derivation(tx_pub_key, view_seckey, derivation); + if (!r) + continue; + + const rct::rctSig& rv = tx.rct_signatures; + + // scan all output, calculate amount + for(size_t i = 0; i < tx.vout.size(); ++i) + { + const cryptonote::tx_out& vo = tx.vout[i]; + + // we only need txout_to_key + if (vo.target.type() != typeid(txout_to_key)) + continue; + + // check if this is our address + crypto::public_key pk; + r = hwd.derive_public_key(derivation, i, spend_pubkey, pk); + if (!r) + continue; + + // check if temp pubkey matched + if (pk != boost::get(vo.target).key) + continue; + + crypto::secret_key sk = null_skey; + hwd.derivation_to_scalar(derivation, i, sk); + if (sk == null_skey) + continue; + + // we decode the amount + rct::key mask; + uint8_t type = rv.type; + switch (type) + { + case rct::RCTTypeSimple: + case rct::RCTTypeBulletproof: + case rct::RCTTypeBulletproof2: + amount += rct::decodeRctSimple(rv, rct::sk2rct(sk), static_cast(i), mask, hwd); + break; + case rct::RCTTypeFull: + amount += rct::decodeRct(rv, rct::sk2rct(sk), static_cast(i), mask, hwd); + break; + // This should never happen + case rct::RCTTypeNull: + amount += vo.amount; + break; + default: + LOG_ERROR("Unsupported rct type: " << type); + break; + } + } + + // we calculate reward rate + stake_reward_rate += cryptonote::get_pos_block_reward_rate(tx.unlock_time, tx_block_height, tx_block_time, amount, height, m_nettype); + } + + // reward has maximum limit + stake_reward_rate = stake_reward_rate > 1.0 ? 1.0 : stake_reward_rate; + + return true; +} + namespace cryptonote { template bool Blockchain::get_transactions(const std::vector&, std::vector&, std::vector&) const; template bool Blockchain::get_transactions_blobs(const std::vector&, std::vector&, std::vector&, bool) const; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 6b5beb48e37..185180d2308 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -346,8 +346,8 @@ namespace cryptonote * * @return true if block template filled in successfully, else false */ - bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); - bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake); + bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake); /** * @brief checks if a block is known about with a given hash @@ -1488,5 +1488,15 @@ namespace cryptonote * At some point, may be used to push an update to miners */ void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t pool_cookie); + + /** + * @brief check miner stake + * + * spend_pubkey is miner's public spend key + * stake's view secret key must match miner's view public key + * stake's every tx must be miner's + * stake_reward is the reward of stake + */ + bool check_miner_stakes(const crypto::public_key& spend_pubkey, const crypto::secret_key& view_seckey, const std::vector& ti, uint64_t height, double& stake_reward_rate); }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockfunding.cpp b/src/cryptonote_core/blockfunding.cpp index e4de0c409fe..d7b5cae1b75 100644 --- a/src/cryptonote_core/blockfunding.cpp +++ b/src/cryptonote_core/blockfunding.cpp @@ -37,6 +37,8 @@ #define MONERO_ENABLE_FUNDING_HEIGHT_REGTESTNET 10 #define MONERO_BLOCK_FUNDING_RATE 0.1 #define MONERO_BLOCK_FUNDING_RATE_NEW 0.7 // from version 60 +#define MONERO_BLOCK_MINER_POW_RATE_NEW 0.1 +#define MONERO_BLOCK_MINER_POS_RATE_NEW 0.2 using namespace cryptonote; using namespace std; @@ -141,21 +143,24 @@ uint64_t BlockFunding::get_funding_enabled_height() } //bool BlockFunding::fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding) -bool BlockFunding::fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding, bool fork) +bool BlockFunding::fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding, uint64_t& pos_reward, double pos_reward_rate, bool fork) { //funding = (uint64_t)(original_reward * MONERO_BLOCK_FUNDING_RATE); funding = fork ? (uint64_t)(original_reward * MONERO_BLOCK_FUNDING_RATE_NEW) : (uint64_t)(original_reward * MONERO_BLOCK_FUNDING_RATE); - miner_reward = (uint64_t)(original_reward - funding); + //miner_reward = (uint64_t)(original_reward - funding); + miner_reward = fork ? (uint64_t)(original_reward * MONERO_BLOCK_MINER_POW_RATE_NEW) : (uint64_t)(original_reward - funding); + pos_reward = fork ? (uint64_t)(original_reward * MONERO_BLOCK_MINER_POS_RATE_NEW * pos_reward_rate) : 0; + //check return true; } //bool BlockFunding::check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t real_reward) -bool BlockFunding::check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t real_reward, bool fork) +bool BlockFunding::check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t std_reward, double stake_reward_rate, bool fork) { - uint64_t real_miner_reward, real_funding; - fund_from_block(real_reward, real_miner_reward, real_funding, fork); - return (actual_miner_reward == real_miner_reward) && (actual_funding == real_funding); + uint64_t real_miner_reward, real_funding, real_stake_reward; + fund_from_block(std_reward, real_miner_reward, real_funding, real_stake_reward, stake_reward_rate, fork); + return (actual_miner_reward == real_miner_reward + real_stake_reward) && (actual_funding == real_funding); } bool BlockFunding::get_funding_from_miner_tx(const transaction& miner_tx, uint64_t& funding_amount) diff --git a/src/cryptonote_core/blockfunding.h b/src/cryptonote_core/blockfunding.h index 05779683826..b12e72ebefe 100644 --- a/src/cryptonote_core/blockfunding.h +++ b/src/cryptonote_core/blockfunding.h @@ -46,10 +46,10 @@ namespace cryptonote{ bool init(const network_type nettype = MAINNET); bool funding_enabled(uint64_t height); // bool check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t real_reward); - bool check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t real_reward, bool fork); + bool check_block_funding(uint64_t actual_miner_reward, uint64_t actual_funding, uint64_t std_reward, double stake_reward_rate, bool fork); bool get_funding_from_miner_tx(const transaction& miner_tx, uint64_t& funding_amount); // bool fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding); - bool fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding, bool fork); + bool fund_from_block(uint64_t original_reward, uint64_t& miner_reward, uint64_t& funding, uint64_t& pos_reward, double pos_reward_rate, bool fork); uint64_t get_funding_enabled_height(); account_public_address& public_address(); private: diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index e8da9f08d4f..b566a8f9328 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1278,14 +1278,14 @@ namespace cryptonote m_mempool.set_relayed(txs); } //----------------------------------------------------------------------------------------------- - bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) + bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake) { - return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); + return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce, ex_stake); } //----------------------------------------------------------------------------------------------- - bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) + bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake) { - return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce); + return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce, ex_stake); } //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 71e8cd07dde..b8d75716d83 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -207,8 +207,8 @@ namespace cryptonote * * @note see Blockchain::create_block_template */ - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); - virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake); + virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, const std::vector& ex_stake); /** * @brief called when a transaction is relayed diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index cef59f0ae25..f30aaee4d99 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -77,13 +77,16 @@ namespace cryptonote } //--------------------------------------------------------------- // bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { - bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version, network_type nettype) { + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version, network_type nettype, const std::vector &extra_stake, double pos_reward_rate) { tx.vin.clear(); tx.vout.clear(); tx.extra.clear(); keypair txkey = keypair::generate(hw::get_device("default")); add_tx_pub_key_to_extra(tx, txkey.pub); + if(!extra_stake.empty()) + if (!add_stake_to_extra(tx.extra, extra_stake)) + return false; if(!extra_nonce.empty()) if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) return false; @@ -103,6 +106,8 @@ namespace cryptonote return false; } + uint64_t std_reward = block_reward; + #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) LOG_PRINT_L1("Creating block template: reward " << block_reward << ", fee " << fee); @@ -122,15 +127,18 @@ namespace cryptonote bool enable_fund = fundctl.funding_enabled(height); uint64_t miner_reward = 0; uint64_t fund_reward = 0; + uint64_t pos_reward = 0; if (enable_fund) { uint64_t adjust_height = nettype == TESTNET ? DIFFICULTY_ADJUST_HEIGHT_TESTNET : DIFFICULTY_ADJUST_HEIGHT; bool fork = height >= adjust_height; - fundctl.fund_from_block(block_reward, miner_reward, fund_reward, fork); - block_reward = miner_reward; - MERROR("construct_miner_tx,block_reward=" << block_reward <<",fund_reward=" << fund_reward << ",height=" << height); + fundctl.fund_from_block(std_reward, miner_reward, fund_reward, pos_reward, pos_reward_rate, fork); + block_reward = miner_reward + fee; + MINFO("construct_miner_tx,block_reward=" << block_reward << ",pos_reward=" << pos_reward <<",fund_reward=" << fund_reward << ",height=" << height); } + block_reward += pos_reward; + std::vector out_amounts; decompose_amount_into_digits(block_reward, hard_fork_version >= 2 ? 0 : ::config::DEFAULT_DUST_THRESHOLD, [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index e02683ac6c9..a013fcac1a9 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -38,7 +38,7 @@ namespace cryptonote { //--------------------------------------------------------------- // bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); - bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1, network_type nettype = MAINNET); + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1, network_type nettype = MAINNET, const std::vector& extra_stake = std::vector(), double pos_reward_rate = 0.0); struct tx_source_entry { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 76f23071dcf..e64b70e6b9f 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1248,6 +1248,7 @@ namespace cryptonote block b; cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); + std::vector ex_stake; cryptonote::difficulty_type wdiff; crypto::hash prev_block; if (!req.prev_block.empty()) @@ -1259,7 +1260,52 @@ namespace cryptonote return false; } } - if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) + + do + { + if (req.view_secret_key.size() != 64 || req.tx_id.empty()) + { + break; + } + + crypto::secret_key vk = AUTO_VAL_INIT(vk); + if (!epee::string_tools::hex_to_pod(req.view_secret_key, vk)) + { + break; + } + + bool all_good = true; + std::vector ids; + for (const auto& str_id: req.tx_id) + { + crypto::hash id = AUTO_VAL_INIT(id); + if (str_id.size() != 64 || !epee::string_tools::hex_to_pod(str_id, id)) + { + all_good = false; + break; + } + + ids.push_back(id); + } + + if (!all_good) + break; + + // all good, so we put data into ex_stake + ex_stake.reserve( sizeof(crypto::public_key) + sizeof(crypto::secret_key) + sizeof(crypto::hash) * ids.size()); + // copy spend public key + std::copy(&info.address.m_spend_public_key.data[0], &info.address.m_spend_public_key.data[sizeof(crypto::public_key)], std::back_inserter(ex_stake)); + // copy view secret key + std::copy(&vk.data[0], &vk.data[sizeof(crypto::secret_key)], std::back_inserter(ex_stake)); + // copy all ids + for (const auto& id: ids) + { + std::copy(&id.data[0], &id.data[sizeof(crypto::hash)], std::back_inserter(ex_stake)); + } + + }while(0); + + if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve, ex_stake)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e5899d196dc..6a4fcf17992 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -941,11 +941,15 @@ namespace cryptonote uint64_t reserve_size; //max 255 bytes std::string wallet_address; std::string prev_block; + std::string view_secret_key; + std::vector tx_id; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(reserve_size) KV_SERIALIZE(wallet_address) KV_SERIALIZE(prev_block) + KV_SERIALIZE(view_secret_key) + KV_SERIALIZE(tx_id) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init request; diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 050f83888a2..c7863489aa1 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -40,6 +40,8 @@ #include #include +#define MONERO_STAKE_MIN_HEIGHT 720 + using namespace epee; namespace Monero { @@ -92,6 +94,12 @@ std::vector TransactionHistoryImpl::getAll() const return m_history; } +std::vector TransactionHistoryImpl::getLockedIncoming() const +{ + boost::shared_lock lock(m_historyMutex); + return m_Incomings; +} + void TransactionHistoryImpl::refresh() { // multithreaded access: @@ -108,6 +116,7 @@ void TransactionHistoryImpl::refresh() for (auto t : m_history) delete t; m_history.clear(); + m_Incomings.clear(); // transactions are stored in wallet2: // - confirmed_transfer_details - out transfers @@ -136,8 +145,13 @@ void TransactionHistoryImpl::refresh() ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0; ti->m_unlock_time = pd.m_unlock_time; + ti->m_coinbase = pd.m_coinbase; m_history.push_back(ti); + if (!pd.m_coinbase && + (pd.m_unlock_time >= pd.m_block_height + MONERO_STAKE_MIN_HEIGHT) && + pd.m_unlock_time > wallet_height) + m_Incomings.push_back(ti); } // confirmed output transactions @@ -177,12 +191,19 @@ void TransactionHistoryImpl::refresh() ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0; + ti->m_coinbase = false; + ti->m_unlock_time = pd.m_unlock_time; // single output transaction might contain multiple transfers for (const auto &d: pd.m_dests) { ti->m_transfers.push_back({d.amount, get_account_address_as_str(m_wallet->m_wallet->nettype(), d.is_subaddress, d.addr)}); } m_history.push_back(ti); + + if (m_wallet->m_wallet->is_dst_address_self(pd) && + (pd.m_unlock_time >= pd.m_block_height + MONERO_STAKE_MIN_HEIGHT) && + pd.m_unlock_time > wallet_height) + m_Incomings.push_back(ti); } // unconfirmed output transactions @@ -211,6 +232,7 @@ void TransactionHistoryImpl::refresh() ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; + ti->m_coinbase = false; m_history.push_back(ti); } @@ -235,6 +257,7 @@ void TransactionHistoryImpl::refresh() ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index); ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; + ti->m_coinbase = pd.m_coinbase; m_history.push_back(ti); LOG_PRINT_L1(__FUNCTION__ << ": Unconfirmed payment found " << pd.m_amount); diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 67fe1989d08..cd9dc3a610d 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -45,6 +45,7 @@ class TransactionHistoryImpl : public TransactionHistory virtual TransactionInfo * transaction(const std::string &id) const; virtual std::vector getAll() const; virtual void refresh(); + virtual std::vector getLockedIncoming() const; private: @@ -52,6 +53,7 @@ class TransactionHistoryImpl : public TransactionHistory std::vector m_history; WalletImpl *m_wallet; mutable boost::shared_mutex m_historyMutex; + std::vector m_Incomings; }; } diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index 21573c6f689..76383874c8d 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -138,6 +138,11 @@ uint64_t TransactionInfoImpl::unlockTime() const return m_unlock_time; } +bool TransactionInfoImpl::isCoinbase() const +{ + return (m_direction == Direction_In) && m_coinbase; +} + } // namespace namespace Bitmonero = Monero; diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index d5c8f31cf58..dc271c8d190 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -60,6 +60,7 @@ class TransactionInfoImpl : public TransactionInfo virtual const std::vector &transfers() const override; virtual uint64_t confirmations() const override; virtual uint64_t unlockTime() const override; + virtual bool isCoinbase() const override; private: int m_direction; @@ -77,6 +78,7 @@ class TransactionInfoImpl : public TransactionInfo std::vector m_transfers; uint64_t m_confirmations; uint64_t m_unlock_time; + bool m_coinbase; friend class TransactionHistoryImpl; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 8a096aae31f..c2d53a8992f 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1438,9 +1438,8 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional amount, uint32_t mixin_count, - PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) - +PendingTransaction * WalletImpl::createLockTransaction(const std::string &dst_addr, const std::string &payment_id, optional amount, uint32_t mixin_count, + PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, uint64_t unlock_time) { clearStatus(); // Pause refresh thread while creating transaction @@ -1460,6 +1459,12 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { + uint64_t daemon_height = daemonBlockChainHeight(); + if (!daemon_height) { + setStatusError(tr("Daemon not synced yet")); + break; + } + if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 setStatusError(tr("Invalid destination address")); @@ -1514,7 +1519,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const de.is_subaddress = info.is_subaddress; de.is_integrated = info.has_payment_id; dsts.push_back(de); - transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_time ? daemon_height + unlock_time : 0, adjusted_priority, extra, subaddr_account, subaddr_indices); } else { @@ -1524,7 +1529,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index) subaddr_indices.insert(index); } - transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, unlock_time ? daemon_height + unlock_time : 0, adjusted_priority, extra, subaddr_account, subaddr_indices); } @@ -1609,6 +1614,13 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const return transaction; } +PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional amount, uint32_t mixin_count, + PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) + +{ + return createLockTransaction(dst_addr, payment_id, amount, mixin_count, priority, subaddr_account, subaddr_indices, 0); +} + PendingTransaction *WalletImpl::createSweepUnmixableTransaction() { @@ -1695,6 +1707,11 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() return transaction; } +uint64_t WalletImpl::reveal_tx_out(const std::string& txid_str) +{ + return m_wallet ? m_wallet->reveal_tx_out(txid_str) : 0; +} + void WalletImpl::disposeTransaction(PendingTransaction *t) { delete t; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 3148d316d2c..d5a3d893e46 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -204,6 +204,13 @@ class WalletImpl : public Wallet virtual bool unlockKeysFile() override; virtual bool isKeysFileLocked() override; virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) override; + virtual PendingTransaction * createLockTransaction(const std::string &dst_addr, const std::string &payment_id, + optional amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, + std::set subaddr_indices = {}, + uint64_t unlock_time = 0) override; + virtual uint64_t reveal_tx_out(const std::string& txid) override; private: void clearStatus() const; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 174ed56cabc..749497247d8 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -198,6 +198,7 @@ struct TransactionInfo virtual std::string paymentId() const = 0; //! only applicable for output transactions virtual const std::vector & transfers() const = 0; + virtual bool isCoinbase() const = 0; }; /** * @brief The TransactionHistory - interface for displaying transaction history @@ -210,6 +211,7 @@ struct TransactionHistory virtual TransactionInfo * transaction(const std::string &id) const = 0; virtual std::vector getAll() const = 0; virtual void refresh() = 0; + virtual std::vector getLockedIncoming() const = 0; }; /** @@ -1014,6 +1016,15 @@ struct Wallet //! cold-device protocol key image sync virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0; + + virtual PendingTransaction * createLockTransaction(const std::string &dst_addr, const std::string &payment_id, + optional amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, + std::set subaddr_indices = {}, + uint64_t unlock_time = 0) = 0; + + virtual uint64_t reveal_tx_out(const std::string& txid) = 0; }; /** diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4df87c50b72..be2c96d5c5c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -10991,6 +10991,88 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt return sig_str; } +bool wallet2::get_tx_by_id(const crypto::hash &txid, cryptonote::transaction &tx) +{ + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = true; + m_daemon_rpc_mutex.lock(); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + + //Failed to get transaction from daemon + if (!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) return false; + + crypto::hash tx_hash; + if (res.txs.size() == 1) + { + ok = get_pruned_tx(res.txs.front(), tx, tx_hash); + if (!ok) return false; + } + else + { + cryptonote::blobdata tx_data; + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) return false; + ok = cryptonote::parse_and_validate_tx_from_blob(tx_data, tx); + if (!ok) return false; + tx_hash = cryptonote::get_transaction_hash(tx); + } + + if (tx_hash != txid) return false; + + return true; +} + +uint64_t wallet2::reveal_tx_out(const std::string& txid_str) +{ + uint64_t amount = 0; + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + return amount; + } + + cryptonote::transaction tx; + if (!get_tx_by_id(txid, tx)) + return amount; + + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if (tx_pub_key == null_pkey) + return amount; + + crypto::key_derivation derivation; + const crypto::secret_key& a = m_account.get_keys().m_view_secret_key; + hw::device &hwd = m_account.get_device(); + bool r = hwd.generate_key_derivation(tx_pub_key, a, derivation); + if (!r) + return amount; + + // scan all output, calculate amount + for(size_t i = 0; i < tx.vout.size(); ++i) + { + const cryptonote::tx_out& vo = tx.vout[i]; + // check if this is our address + crypto::public_key pk; + r = hwd.derive_public_key(derivation, i, m_account.get_keys().m_account_address.m_spend_public_key, pk); + if (!r) + continue; + + // check if temp pubkey matched + if (pk != boost::get(vo.target).key) + continue; + + rct::key mask; + amount += decodeRct(tx.rct_signatures, derivation, i, mask, hwd); + } + + return amount; +} + bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) { // fetch tx pubkey from the daemon diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 4fd7f1d942f..8344c9b1221 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -776,6 +776,18 @@ namespace tools void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_lookahead(size_t major, size_t minor); std::pair get_subaddress_lookahead() const { return {m_subaddress_lookahead_major, m_subaddress_lookahead_minor}; } + bool is_dst_address_self(const confirmed_transfer_details& ctd) { + bool found = true; + for (const auto& dst : ctd.m_dests) { + auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); + if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != ctd.m_subaddr_account) + { + found = false; + break; + } + } + return found; + }; /*! * \brief Tells if the wallet file is deprecated. */ @@ -1074,6 +1086,8 @@ namespace tools bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); bool check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const; + bool get_tx_by_id(const crypto::hash &txid, cryptonote::transaction &tx); + uint64_t reveal_tx_out(const std::string& txid_str); std::string get_spend_proof(const crypto::hash &txid, const std::string &message); bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 32b601d7aed..5bbfda5474b 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -270,7 +270,7 @@ bool transactions_flow_test(std::string& working_folder, misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon } - uint64_t money_2 = w2.balance(0); + uint64_t money_2 = w2.balance(0).template convert_to(); if(money_2 == transfered_money) { MGINFO_GREEN("-----------------------FINISHING TRANSACTIONS FLOW TEST OK-----------------------");