diff options
25 files changed, 874 insertions, 37 deletions
diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index b3623298c..6292e471c 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -33,6 +33,7 @@ #include <locale> #include <cstdlib> #include <iomanip> +#include <type_traits> //#include <strsafe.h> #include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid_io.hpp> @@ -171,6 +172,7 @@ namespace string_tools template<class t_pod_type> bool parse_tpod_from_hex_string(const std::string& str_hash, t_pod_type& t_pod) { + static_assert(std::is_pod<t_pod_type>::value, "expected pod type"); std::string buf; bool res = epee::string_tools::parse_hexstr_to_binbuff(str_hash, buf); if (!res || buf.size() != sizeof(t_pod_type)) @@ -570,6 +572,7 @@ POP_WARNINGS template<class t_pod_type> std::string pod_to_hex(const t_pod_type& s) { + static_assert(std::is_pod<t_pod_type>::value, "expected pod type"); std::string buff; buff.assign(reinterpret_cast<const char*>(&s), sizeof(s)); return buff_to_hex_nodelimer(buff); @@ -578,6 +581,7 @@ POP_WARNINGS template<class t_pod_type> bool hex_to_pod(const std::string& hex_str, t_pod_type& s) { + static_assert(std::is_pod<t_pod_type>::value, "expected pod type"); std::string hex_str_tr = trim(hex_str); if(sizeof(s)*2 != hex_str.size()) return false; diff --git a/contrib/valgrind/monero.supp b/contrib/valgrind/monero.supp new file mode 100644 index 000000000..1c400076e --- /dev/null +++ b/contrib/valgrind/monero.supp @@ -0,0 +1,10 @@ +{ + libunwind causes spurious report + Memcheck:Param + msync(start) + ... + obj:/usr/lib64/libunwind.so.* + ... + fun:_ULx86_64_step + ... +} diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index ccfd4a279..198f15ca3 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -58,6 +58,11 @@ monero_private_headers(blockchain_export ${blockchain_export_private_headers}) +set(cn_deserialize_sources + cn_deserialize.cpp + ) + + monero_add_executable(blockchain_import ${blockchain_import_sources} ${blockchain_import_private_headers}) @@ -104,3 +109,20 @@ add_dependencies(blockchain_export set_property(TARGET blockchain_export PROPERTY OUTPUT_NAME "monero-blockchain-export") + +monero_add_executable(cn_deserialize + ${cn_deserialize_sources} + ${cn_deserialize_private_headers}) + +target_link_libraries(cn_deserialize + LINK_PRIVATE + cryptonote_core + p2p + ${CMAKE_THREAD_LIBS_INIT}) + +add_dependencies(cn_deserialize + version) +set_property(TARGET cn_deserialize + PROPERTY + OUTPUT_NAME "cn_deserialize") + diff --git a/src/blockchain_utilities/cn_deserialize.cpp b/src/blockchain_utilities/cn_deserialize.cpp new file mode 100644 index 000000000..a8448dcee --- /dev/null +++ b/src/blockchain_utilities/cn_deserialize.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. 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. +// +// 3. Neither the name of the copyright holder 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 HOLDER OR CONTRIBUTORS 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. + +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/tx_extra.h" +#include "cryptonote_core/blockchain.h" +#include "blockchain_utilities.h" +#include "common/command_line.h" +#include "version.h" + +namespace po = boost::program_options; +using namespace epee; // log_space + +using namespace cryptonote; + +int main(int argc, char* argv[]) +{ + uint32_t log_level = 0; + std::string input; + + tools::sanitize_locale(); + + boost::filesystem::path output_file_path; + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; + const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; + const command_line::arg_descriptor<std::string> arg_input = {"input", "Specify input has a hexadecimal string", ""}; + + command_line::add_arg(desc_cmd_sett, arg_output_file); + command_line::add_arg(desc_cmd_sett, arg_log_level); + command_line::add_arg(desc_cmd_sett, arg_input); + + command_line::add_arg(desc_cmd_only, command_line::arg_help); + + po::options_description desc_options("Allowed options"); + desc_options.add(desc_cmd_only).add(desc_cmd_sett); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (! r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 1; + } + + log_level = command_line::get_arg(vm, arg_log_level); + input = command_line::get_arg(vm, arg_input); + if (input.empty()) + { + std::cerr << "--input is mandatory" << std::endl; + return 1; + } + + log_space::get_set_log_detalisation_level(true, log_level); + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + + std::string m_config_folder; + + std::ostream *output; + std::ofstream *raw_data_file = NULL; + if (command_line::has_arg(vm, arg_output_file)) + { + output_file_path = boost::filesystem::path(command_line::get_arg(vm, arg_output_file)); + + const boost::filesystem::path dir_path = output_file_path.parent_path(); + if (!dir_path.empty()) + { + if (boost::filesystem::exists(dir_path)) + { + if (!boost::filesystem::is_directory(dir_path)) + { + std::cerr << "output directory path is a file: " << dir_path << std::endl; + return 1; + } + } + else + { + if (!boost::filesystem::create_directory(dir_path)) + { + std::cerr << "Failed to create directory " << dir_path << std::endl; + return 1; + } + } + } + + raw_data_file = new std::ofstream(); + raw_data_file->open(output_file_path.string(), std::ios_base::out | std::ios::trunc); + if (raw_data_file->fail()) + return 1; + output = raw_data_file; + } + else + { + output_file_path = ""; + output = &std::cout; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(input, blob)) + { + std::cerr << "Invalid hex input" << std::endl; + std::cerr << "Invalid hex input: " << input << std::endl; + return 1; + } + + cryptonote::block block; + cryptonote::transaction tx; + std::vector<cryptonote::tx_extra_field> fields; + if (cryptonote::parse_and_validate_block_from_blob(blob, block)) + { + std::cout << "Parsed block:" << std::endl; + std::cout << cryptonote::obj_to_json_str(block) << std::endl; + } + else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx)) + { + std::cout << "Parsed transaction:" << std::endl; + std::cout << cryptonote::obj_to_json_str(tx) << std::endl; + + if (cryptonote::parse_tx_extra(tx.extra, fields)) + { + std::cout << "tx_extra has " << fields.size() << " field(s)" << std::endl; + for (size_t n = 0; n < fields.size(); ++n) + { + std::cout << "field " << n << ": "; + if (typeid(cryptonote::tx_extra_padding) == fields[n].type()) std::cout << "extra padding: " << boost::get<cryptonote::tx_extra_padding>(fields[n]).size << " bytes"; + else if (typeid(cryptonote::tx_extra_pub_key) == fields[n].type()) std::cout << "extra pub key: " << boost::get<cryptonote::tx_extra_pub_key>(fields[n]).pub_key; + else if (typeid(cryptonote::tx_extra_nonce) == fields[n].type()) std::cout << "extra nonce: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_nonce>(fields[n]).nonce); + else if (typeid(cryptonote::tx_extra_merge_mining_tag) == fields[n].type()) std::cout << "extra merge mining tag: depth " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).depth << ", merkle root " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).merkle_root; + else if (typeid(cryptonote::tx_extra_mysterious_minergate) == fields[n].type()) std::cout << "extra minergate custom: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_mysterious_minergate>(fields[n]).data); + else std::cout << "unknown"; + std::cout << std::endl; + } + } + else + { + std::cout << "Failed to parse tx_extra" << std::endl; + } + } + else + { + std::cerr << "Not a recognized CN type" << std::endl; + return 1; + } + + + + if (output->fail()) + return 1; + output->flush(); + if (raw_data_file) + delete raw_data_file; + + return 0; +} diff --git a/src/crypto/skein.c b/src/crypto/skein.c index 9c8ac288d..65e4525c3 100644 --- a/src/crypto/skein.c +++ b/src/crypto/skein.c @@ -77,7 +77,7 @@ typedef struct /* 1024-bit Skein hash context stru } Skein1024_Ctxt_t; /* Skein APIs for (incremental) "straight hashing" */ -#if SKEIN_256_NIST_MAX_HASH_BITS +#if SKEIN_256_NIST_MAX_HASHBITS static int Skein_256_Init (Skein_256_Ctxt_t *ctx, size_t hashBitLen); #endif static int Skein_512_Init (Skein_512_Ctxt_t *ctx, size_t hashBitLen); @@ -1941,7 +1941,7 @@ static HashReturn Final (hashState *state, BitSequence *hashval); /* select the context size and init the context */ static HashReturn Init(hashState *state, int hashbitlen) { -#if SKEIN_256_NIST_MAX_HASH_BITS +#if SKEIN_256_NIST_MAX_HASHBITS if (hashbitlen <= SKEIN_256_NIST_MAX_HASHBITS) { Skein_Assert(hashbitlen > 0,BAD_HASHLEN); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 66084da3c..175a5d26e 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -64,6 +64,8 @@ #define FEE_PER_KB_OLD ((uint64_t)10000000000) // pow(10, 10) #define FEE_PER_KB ((uint64_t)2000000000) // 2 * pow(10, 9) +#define DYNAMIC_FEE_PER_KB_BASE_FEE ((uint64_t)2000000000) // 2 * pow(10,9) +#define DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD ((uint64_t)10000000000000) // 10 * pow(10,12) #define ORPHANED_BLOCKS_MAX_COUNT 100 @@ -122,6 +124,8 @@ #define THREAD_STACK_SIZE 5 * 1024 * 1024 +#define HF_VERSION_DYNAMIC_FEE 4 + // New constants are intended to go here namespace config { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9ea023a4c..ffebcd592 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -46,6 +46,7 @@ #include "misc_language.h" #include "profile_tools.h" #include "file_io_utils.h" +#include "common/int-util.h" #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" @@ -2709,6 +2710,87 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const } //------------------------------------------------------------------ +uint64_t Blockchain::get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size) +{ + if (median_block_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2) + median_block_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + + uint64_t unscaled_fee_per_kb = (DYNAMIC_FEE_PER_KB_BASE_FEE * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / median_block_size); + uint64_t hi, lo = mul128(unscaled_fee_per_kb, block_reward, &hi); + static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); + static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits<uint32_t>::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); + // divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't + div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); + div128_32(hi, lo, 1000000, &hi, &lo); + assert(hi == 0); + + return lo; +} + +//------------------------------------------------------------------ +bool Blockchain::check_fee(size_t blob_size, uint64_t fee) const +{ + const uint8_t version = get_current_hard_fork_version(); + + uint64_t fee_per_kb; + if (version < HF_VERSION_DYNAMIC_FEE) + { + fee_per_kb = FEE_PER_KB; + } + else + { + uint64_t median = m_current_block_cumul_sz_limit / 2; + uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) + return false; + fee_per_kb = get_dynamic_per_kb_fee(base_reward, median); + } + LOG_PRINT_L2("Using " << print_money(fee) << "/kB fee"); + + uint64_t needed_fee = blob_size / 1024; + needed_fee += (blob_size % 1024) ? 1 : 0; + needed_fee *= fee_per_kb; + + if (fee < needed_fee) + { + LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); + return false; + } + return true; +} + +//------------------------------------------------------------------ +uint64_t Blockchain::get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks) const +{ + const uint8_t version = get_current_hard_fork_version(); + + if (version < HF_VERSION_DYNAMIC_FEE) + return FEE_PER_KB; + + if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) + grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; + + std::vector<size_t> sz; + get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); + for (size_t i = 0; i < grace_blocks; ++i) + sz.push_back(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2); + + uint64_t median = epee::misc_utils::median(sz); + if(median <= CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2) + median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + + uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) + return false; + + uint64_t fee = get_dynamic_per_kb_fee(base_reward, median); + LOG_PRINT_L2("Estimating " << grace_blocks << "-block fee at " << print_money(fee) << "/kB"); + return fee; +} + +//------------------------------------------------------------------ // This function checks to see if a tx is unlocked. unlock_time is either // a block index or a unix time. bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 262c2952b..eb7a050b2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -513,6 +513,47 @@ namespace cryptonote bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); /** + * @brief get dynamic per kB fee for a given block size + * + * The dynamic fee is based on the block size in a past window, and + * the current block reward. It is expressed by kB. + * + * @param block_reward the current block reward + * @param median_block_size the median blob's size in the past window + * + * @return the per kB fee + */ + static uint64_t get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size); + + /** + * @brief get dynamic per kB fee estimate for the next few blocks + * + * The dynamic fee is based on the block size in a past window, and + * the current block reward. It is expressed by kB. This function + * calculates an estimate for a dynamic fee which will be valid for + * the next grace_blocks + * + * @param grace_blocks number of blocks we want the fee to be valid for + * + * @return the per kB fee estimate + */ + uint64_t get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks) const; + + /** + * @brief validate a transaction's fee + * + * This function validates the fee is enough for the transaction. + * This is based on the size of the transaction blob, and, after a + * height threshold, on the average size of transaction in a past window + * + * @param blob_size the transaction blob's size + * @param fee the fee + * + * @return true if the fee is enough, false otherwise + */ + bool check_fee(size_t blob_size, uint64_t fee) const; + + /** * @brief check that a transaction's outputs conform to current standards * * This function checks, for example at the time of this writing, that diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 870e8f0d8..6d64a43cb 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -509,7 +509,7 @@ namespace cryptonote std::string extra_nonce; set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - remove_field_from_tx_extra(tx.extra, typeid(tx_extra_fields)); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_nonce)); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) { LOG_ERROR("Failed to add encrypted payment id to tx extra"); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index dba05a539..e72a592ca 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -133,12 +133,8 @@ namespace cryptonote fee = tx.rct_signatures.txnFee; } - uint64_t needed_fee = blob_size / 1024; - needed_fee += (blob_size % 1024) ? 1 : 0; - needed_fee *= FEE_PER_KB; - if (!kept_by_block && fee < needed_fee) + if (!kept_by_block && !m_blockchain.check_fee(blob_size, fee)) { - LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); tvc.m_verifivation_failed = true; tvc.m_fee_too_low = true; return false; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f7ea3729d..19e9d291e 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -610,6 +610,7 @@ namespace rct { // Thus the amounts vector will be "one" longer than the destinations vectort rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); @@ -671,6 +672,7 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk"); CHECK_AND_ASSERT_THROW_MES(mixRing.size() == inSk.size(), "Different number of mixRing/inSk"); for (size_t n = 0; n < mixRing.size(); ++n) { @@ -772,7 +774,7 @@ namespace rct { threads = std::min(threads, rv.outPk.size()); for (size_t i = 0; i < threads; ++i) threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - bool ioservice_active = threads > 1; + bool ioservice_active = true; std::deque<bool> results(rv.outPk.size(), false); epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); }); @@ -838,7 +840,7 @@ namespace rct { threads = std::min(threads, rv.outPk.size()); for (size_t i = 0; i < threads; ++i) threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - bool ioservice_active = threads > 1; + bool ioservice_active = true; std::deque<bool> results(rv.outPk.size(), false); epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); }); @@ -880,7 +882,7 @@ namespace rct { threads = std::min(threads, rv.mixRing.size()); for (size_t i = 0; i < threads; ++i) threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - bool ioservice_active = threads > 1; + bool ioservice_active = true; std::deque<bool> results(rv.mixRing.size(), false); epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); }); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0fca2eb57..a02a2375b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1283,6 +1283,13 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp) + { + res.fee = m_core.get_blockchain_storage().get_dynamic_per_kb_fee_estimate(req.grace_blocks); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res) { // TODO diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 147f019d6..2fdb790ab 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -116,6 +116,7 @@ namespace cryptonote MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM) MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION) MAP_JON_RPC_WE("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM) + MAP_JON_RPC_WE("get_fee_estimate", on_get_per_kb_fee_estimate, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE) END_JSON_RPC_MAP() END_URI_MAP2() @@ -162,6 +163,7 @@ namespace cryptonote bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp); bool on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp); bool on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp); + bool on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp); //----------------------- private: diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 85895a71a..718c98b6a 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1266,4 +1266,27 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE + { + struct request + { + uint64_t grace_blocks; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(grace_blocks) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t fee; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(fee) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 09c574528..12a04ee81 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -78,6 +78,7 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\001" +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\001" // workaround for a suspected bug in pthread/kernel on MacOS X #ifdef __APPLE__ @@ -703,6 +704,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status")); + m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet")); + m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -3111,16 +3114,16 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) +bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx) { // gather info to ask the user uint64_t amount = 0, amount_to_dests = 0, change = 0; size_t min_mixin = ~0; std::unordered_map<std::string, uint64_t> dests; const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); - for (size_t n = 0; n < txs.txes.size(); ++n) + for (size_t n = 0; n < get_num_txes(); ++n) { - const tools::wallet2::tx_construction_data &cd = txs.txes[n]; + const tools::wallet2::tx_construction_data &cd = get_tx(n); for (size_t s = 0; s < cd.sources.size(); ++s) { amount += cd.sources[s].amount; @@ -3154,6 +3157,8 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) } change = cd.change_dts.amount; it->second -= cd.change_dts.amount; + if (it->second == 0) + dests.erase(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); } } std::string dest_string; @@ -3168,11 +3173,21 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) dest_string = tr("with no destinations"); uint64_t fee = amount - amount_to_dests; - std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, change %s, %s, with min mixin %lu. Is this okay? (Y/Yes/N/No)")) % (unsigned long)txs.txes.size() % print_money(amount) % print_money(fee) % print_money(change) % dest_string % (unsigned long)min_mixin).str(); + std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, change %s, %s, with min mixin %lu. Is this okay? (Y/Yes/N/No)")) % (unsigned long)get_num_txes() % print_money(amount) % print_money(fee) % print_money(change) % dest_string % (unsigned long)min_mixin).str(); std::string accepted = command_line::input_line(prompt_str); return is_it_true(accepted); } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) +{ + return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) +{ + return accept_loaded_tx([&txs](){return txs.ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.ptx[n].construction_data;}); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) { if(m_wallet->watch_only()) @@ -3208,7 +3223,7 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) try { std::vector<tools::wallet2::pending_tx> ptx_vector; - bool r = m_wallet->load_tx("signed_monero_tx", ptx_vector); + bool r = m_wallet->load_tx("signed_monero_tx", ptx_vector, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); }); if (!r) { fail_msg_writer() << tr("Failed to load transaction from file"); @@ -3673,7 +3688,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::confirmed_transfer_details &pd = i->second; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known - uint64_t fee = pd.m_amount_in - pd.m_amount_out - change; + uint64_t fee = pd.m_amount_in - pd.m_amount_out; std::string dests; for (const auto &d: pd.m_dests) { if (!dests.empty()) @@ -3723,7 +3738,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; uint64_t amount = pd.m_amount_in; - uint64_t fee = amount - pd.m_amount_out - pd.m_change; + uint64_t fee = amount - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); @@ -4101,6 +4116,103 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::export_outputs(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_outputs <filename>"); + return true; + } + std::string filename = args[0]; + + try + { + std::vector<tools::wallet2::transfer_details> outs = m_wallet->export_outputs(); + + std::stringstream oss; + boost::archive::binary_oarchive ar(oss); + ar << outs; + + std::string data(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + bool r = epee::file_io_utils::save_string_to_file(filename, data + oss.str()); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting outputs: " << e.what()); + fail_msg_writer() << "Error exporting outputs: " << e.what(); + return true; + } + + success_msg_writer() << tr("Outputs exported to ") << filename; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::import_outputs(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: import_outputs <filename>"); + return true; + } + std::string filename = args[0]; + + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << "Bad output export file magic in " << filename; + return true; + } + const size_t headerlen = magiclen + 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + fail_msg_writer() << "Bad data size from file " << filename; + return true; + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[magiclen]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[magiclen + sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + fail_msg_writer() << "Outputs from " << filename << " are for a different account"; + return true; + } + + try + { + std::string body(data, headerlen); + std::stringstream iss; + iss << body; + boost::archive::binary_iarchive ar(iss); + std::vector<tools::wallet2::transfer_details> outputs; + ar >> outputs; + + size_t n_outputs = m_wallet->import_outputs(outputs); + success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported"; + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to import outputs: " << e.what(); + return true; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::process_command(const std::vector<std::string> &args) { return m_cmd_binder.process_command_vec(args); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 375716604..fcc77ff69 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -152,11 +152,15 @@ namespace cryptonote bool verify(const std::vector<std::string> &args); bool export_key_images(const std::vector<std::string> &args); bool import_key_images(const std::vector<std::string> &args); + bool export_outputs(const std::vector<std::string> &args); + bool import_outputs(const std::vector<std::string> &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false); bool ask_wallet_create_if_needed(); + bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx); bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs); + bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); bool get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id); /*! diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 2ba5f3620..63c4ea3cc 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -157,7 +157,7 @@ void TransactionHistoryImpl::refresh() const tools::wallet2::confirmed_transfer_details &pd = i->second; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known - uint64_t fee = pd.m_amount_in - pd.m_amount_out - change; + uint64_t fee = pd.m_amount_in - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index b8500aae3..d2395ace1 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -81,6 +81,12 @@ bool WalletManagerImpl::closeWallet(Wallet *wallet) bool WalletManagerImpl::walletExists(const std::string &path) { + bool keys_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + if(keys_file_exists){ + return true; + } return false; } @@ -91,7 +97,7 @@ std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) boost::filesystem::path work_dir(path); // return empty result if path doesn't exist if(!boost::filesystem::is_directory(path)){ - return result; + return result; } const boost::regex wallet_rx("(.*)\\.(keys)$"); // searching for <wallet_name>.keys files boost::filesystem::recursive_directory_iterator end_itr; // Default ctor yields past-the-end diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a02c2e4e5..6d51e355f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -80,6 +80,8 @@ using namespace cryptonote; #define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone +#define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -246,6 +248,15 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, } } //---------------------------------------------------------------------------------------------------- +bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki) +{ + if (!cryptonote::generate_key_image_helper(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + if (m_watch_only) + memset(&ki, 0, 32); + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) { class lazy_txid_getter @@ -317,7 +328,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s // this assumes that the miner tx pays a single address if (received) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -360,7 +371,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -408,7 +419,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -440,7 +451,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { if (received) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -518,14 +529,14 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(ki) + LOG_ERROR("key image " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " " << print_money(m_transfers[kit->second].amount()) << ", received output ignored"); } else { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(ki) + LOG_ERROR("key image " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one @@ -695,6 +706,17 @@ void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t heigh else entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee; entry.first->second.m_change = received; + + std::vector<tx_extra_field> tx_extra_fields; + if(parse_tx_extra(tx.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); + } + } } entry.first->second.m_block_height = height; entry.first->second.m_timestamp = ts; @@ -2138,9 +2160,14 @@ void wallet2::rescan_spent() std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(key_images.size())); // update spent status + key_image zero_ki; + memset(&zero_ki, 0, 32); for (size_t i = 0; i < m_transfers.size(); ++i) { transfer_details& td = m_transfers[i]; + // a view wallet may not know about key images + if (td.m_key_image == zero_ki) + continue; if (td.m_spent != (daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { if (td.m_spent) @@ -2341,6 +2368,7 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo utd.m_amount_out = 0; for (const auto &d: dests) utd.m_amount_out += d.amount; + utd.m_amount_out += change_amount; utd.m_change = change_amount; utd.m_sent_time = time(NULL); utd.m_tx = (const cryptonote::transaction_prefix&)tx; @@ -2678,7 +2706,7 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s); } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx) +bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) { std::string s; boost::system::error_code errcode; @@ -2709,6 +2737,12 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + if (accept_func && !accept_func(signed_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + ptx = signed_txs.ptx; return true; @@ -2733,6 +2767,40 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const return 1; } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_dynamic_per_kb_fee_estimate() +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_fee_estimate"; + req_t.params.grace_blocks = FEE_ESTIMATE_GRACE_BLOCKS; + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status == CORE_RPC_STATUS_OK, "Failed to get fee estimate"); + return resp_t.result.fee; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_per_kb_fee() +{ + bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 14); + if (!use_dyn_fee) + return FEE_PER_KB; + try + { + return get_dynamic_per_kb_fee_estimate(); + } + catch (...) + { + LOG_PRINT_L1("Failed to query per kB fee, using " << print_money(FEE_PER_KB)); + return FEE_PER_KB; + } +} +//---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // // this function will make multiple calls to wallet2::transfer if multiple @@ -2742,7 +2810,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, trusted_daemon); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); // failsafe split attempt counter @@ -3463,7 +3531,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_rct = use_fork_rules(4, 0); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); // throw if attempting a transaction with no destinations @@ -3745,7 +3813,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); @@ -4051,7 +4119,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); // may throw std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); @@ -4243,7 +4311,11 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key crypto::key_image ki; cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); - THROW_WALLET_EXCEPTION_IF(ki != td.m_key_image, + + bool zero_key_image = true; + for (size_t i = 0; i < sizeof(td.m_key_image); ++i) + zero_key_image &= (td.m_key_image.data[i] == 0); + THROW_WALLET_EXCEPTION_IF(!zero_key_image && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -4330,6 +4402,50 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag return m_transfers[signed_key_images.size() - 1].m_block_height; } //---------------------------------------------------------------------------------------------------- +std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const +{ + std::vector<tools::wallet2::transfer_details> outs; + + outs.reserve(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + const transfer_details &td = m_transfers[n]; + + outs.push_back(td); + } + + return outs; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs) +{ + m_transfers.clear(); + m_transfers.reserve(outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) + { + transfer_details td = outputs[i]; + + // the hot wallet wouldn't have known about key images (except if we already exported them) + cryptonote::keypair in_ephemeral; + std::vector<tx_extra_field> tx_extra_fields; + tx_extra_pub_key pub_key_field; + + THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + i); + THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, + "Transaction extra has unsupported format at index " + i); + THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field), error::wallet_internal_error, + "Public key wasn't found in the transaction extra at index " + i); + + cryptonote::generate_key_image_helper(m_account.get_keys(), pub_key_field.pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image); + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + i); + + m_transfers.push_back(td); + } + + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 39a2a37f1..fb7de44ad 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -357,7 +357,7 @@ namespace tools void commit_tx(std::vector<pending_tx>& ptx_vector); bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::function<bool(const unsigned_tx_set&)> accept_func = NULL); - bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx); + bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); @@ -473,6 +473,9 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + std::vector<tools::wallet2::transfer_details> export_outputs() const; + size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs); + std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const; uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent); @@ -517,12 +520,15 @@ namespace tools uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); uint64_t get_fee_multiplier(uint32_t priority, bool use_new_fee) const; + uint64_t get_dynamic_per_kb_fee_estimate(); + uint64_t get_per_kb_fee(); float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; void set_spent(size_t idx, uint64_t height); void set_unspent(size_t idx); template<typename entry> void get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); cryptonote::account_base m_account; std::string m_daemon_address; @@ -566,8 +572,8 @@ namespace tools BOOST_CLASS_VERSION(tools::wallet2, 14) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 4) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 5) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) +BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3) namespace boost { @@ -671,6 +677,14 @@ namespace boost return; a & x.m_amount_in; a & x.m_amount_out; + if (ver < 6) + { + // v<6 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. + // We convert it to include change from v6 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + x.m_amount_out += x.m_change; + } } template <class Archive> @@ -687,6 +701,20 @@ namespace boost if (ver < 2) return; a & x.m_timestamp; + if (ver < 3) + { + // v<3 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. Whether it got added + // or not depends on whether it came from a unconfirmed_transfer_details + // (not included) or not (included). We can't reliably tell here, so we + // check whether either yields a "negative" fee, or use the other if so. + // We convert it to include change from v3 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + { + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; + } + } } template <class Archive> diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index b7fdc333f..f3658b9ff 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -39,6 +39,7 @@ set(unit_tests_sources dns_resolver.cpp epee_boosted_tcp_server.cpp epee_levin_protocol_handler_async.cpp + fee.cpp get_xtype_from_string.cpp main.cpp mnemonics.cpp diff --git a/tests/unit_tests/fee.cpp b/tests/unit_tests/fee.cpp new file mode 100644 index 000000000..6f1413c69 --- /dev/null +++ b/tests/unit_tests/fee.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. 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. +// +// 3. Neither the name of the copyright holder 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 HOLDER OR CONTRIBUTORS 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "gtest/gtest.h" + +#include "cryptonote_core/blockchain.h" + +using namespace cryptonote; + +namespace +{ + //-------------------------------------------------------------------------------------------------------------------- + class fee : public ::testing::Test + { + }; + + TEST_F(fee, 10xmr) + { + // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 2000000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 2000000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 2000000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, 1), 2000000000); + + // higher is inverse proportional + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 2000000000 / 2); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 2000000000 / 10); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 2000000000 / 1000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 2000000000 / 1000000); + } + + TEST_F(fee, 1xmr) + { + // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 200000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 200000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 200000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, 1), 200000000); + + // higher is inverse proportional + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 200000000 / 2); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 200000000 / 10); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 200000000 / 1000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 200000000 / 1000000); + } + + TEST_F(fee, dot3xmr) + { + // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2), 60000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2), 60000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100), 60000000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, 1), 60000000); + + // higher is inverse proportional + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2), 60000000 / 2); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10), 60000000 / 10); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000), 60000000 / 1000); + ASSERT_EQ(Blockchain::get_dynamic_per_kb_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull), 60000000 / 1000000); + } + + static bool is_more_or_less(double x, double y) + { + return fabs(y - x) < 0.001; + } + + static const double MAX_MULTIPLIER = 166.f; + + TEST_F(fee, double_at_full) + { + static const uint64_t block_rewards[] = { + 20000000000000ull, // 20 monero + 13000000000000ull, + 1000000000000ull, + 600000000000ull, // .6 monero, minimum reward per block at 2min + 300000000000ull, // .3 monero, minimum reward per block at 1min + }; + static const uint64_t median_block_sizes[] = { + CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2, + CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2, + CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10, + CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000, + CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000000ull + }; + + for (uint64_t block_reward: block_rewards) + { + for (uint64_t median_block_size: median_block_sizes) + { + ASSERT_TRUE(is_more_or_less(Blockchain::get_dynamic_per_kb_fee(block_reward, median_block_size) * (median_block_size / 1024.) * MAX_MULTIPLIER / (double)block_reward, 1.992 * 1000 / 1024)); + } + } + } +} diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index 224e32e61..1abf2511d 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -246,7 +246,6 @@ TEST(ringct, range_proofs_with_fee) //add txn fee for 1 //has no corresponding destination.. amounts.push_back(1); - amount_keys.push_back(hash_to_scalar(zero())); //add output for 12500 amounts.push_back(12500); @@ -356,10 +355,12 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount for (int n = 0; n < n_outputs; ++n) { amounts.push_back(output_amounts[n]); - amount_keys.push_back(rct::hash_to_scalar(rct::zero())); skpkGen(Sk, Pk); if (n < n_outputs - 1 || !last_is_fee) + { destinations.push_back(Pk); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + } } return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3);; diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index f51f9ec67..dc7f7cb06 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -559,6 +559,7 @@ TEST(Serialization, serializes_ringct_types) rct::keyV amount_keys; //add output 500 amounts.push_back(500); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); rct::keyV destinations; rct::key Sk, Pk; rct::skpkGen(Sk, Pk); diff --git a/utils/gpg_keys/anonimal.asc b/utils/gpg_keys/anonimal.asc new file mode 100644 index 000000000..0fb9f827e --- /dev/null +++ b/utils/gpg_keys/anonimal.asc @@ -0,0 +1,64 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +=EuZn +-----END PGP PUBLIC KEY BLOCK----- |