diff options
-rw-r--r-- | .travis.yml | 120 | ||||
-rw-r--r-- | README.md | 28 | ||||
-rw-r--r-- | contrib/valgrind/monero.supp | 10 | ||||
-rw-r--r-- | src/cryptonote_config.h | 4 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 82 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 41 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 6 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 8 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 7 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.h | 2 | ||||
-rw-r--r-- | src/rpc/core_rpc_server_commands_defs.h | 23 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 4 | ||||
-rw-r--r-- | src/wallet/api/transaction_history.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 56 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 28 | ||||
-rw-r--r-- | tests/unit_tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/unit_tests/fee.cpp | 121 | ||||
-rw-r--r-- | tests/unit_tests/ringct.cpp | 5 | ||||
-rw-r--r-- | tests/unit_tests/serialization.cpp | 1 |
19 files changed, 406 insertions, 143 deletions
diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3eb75bea7..000000000 --- a/.travis.yml +++ /dev/null @@ -1,120 +0,0 @@ -sudo: required -dist: trusty -language: cpp - -matrix: - include: - - # - # Coveralls.io - # - - os: linux - compiler: gcc - addons: - apt: - packages: - - build-essential - - cmake - - doxygen - - g++ - - gcc - - clang - - graphviz - - libdb++-dev - - libdb-dev - - libgtest-dev - - libminiupnpc-dev - - libssl-dev - - libssl1.0.0 - - libunbound-dev - - libunwind8-dev - sources: - - ubuntu-toolchain-r-test - before_install: - - sudo add-apt-repository -y ppa:kojoley/boost - - sudo apt-get -q update - - pip install --user cpp-coveralls - install: - - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} - env: - # exclude long-running and failing tests (#895) - - ARGS=" -E 'coretests|libwallet_api_tests' " - script: - - make -j2 coverage - after_success: - - travis_wait coveralls -e external -e tests -e cmake -e contrib -e translations -e utils --gcov-options '\-lp' &> /dev/null - - # - # Monero release-all (gcc) - # - - os: linux - compiler: gcc - addons: - apt: - packages: - - build-essential - - cmake - - doxygen - - g++ - - gcc - - clang - - graphviz - - libdb++-dev - - libdb-dev - - libgtest-dev - - libminiupnpc-dev - - libssl-dev - - libssl1.0.0 - - libunbound-dev - - libunwind8-dev - sources: - - ubuntu-toolchain-r-test - before_install: - - sudo add-apt-repository -y ppa:kojoley/boost - - sudo apt-get -q update - install: - - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} - script: - - make -j2 && HAVE_DOT=YES doxygen Doxyfile - - # - # Monero release-all (clang) - # - - os: linux - compiler: clang - addons: - apt: - packages: - - build-essential - - cmake - - doxygen - - clang - - graphviz - - libdb++-dev - - libdb-dev - - libgtest-dev - - libminiupnpc-dev - - libssl-dev - - libssl1.0.0 - - libunbound-dev - - libunwind8-dev - sources: - - ubuntu-toolchain-r-test - before_install: - - sudo add-apt-repository -y ppa:kojoley/boost - - sudo apt-get -q update - install: - - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} - script: - - make -j2 && HAVE_DOT=YES doxygen Doxyfile - -notifications: - email: false - irc: - on_success: change - on_failure: change - channels: - - "chat.freenode.net#monero-bots" - nick: monero - template: - - "%{result} | %{repository}#%{build_number} (%{commit} : %{author}) | Build details : %{build_url}" @@ -2,16 +2,36 @@ Copyright (c) 2014-2016, The Monero Project -[![Build Status](https://travis-ci.org/monero-project/monero.svg?branch=master)](https://travis-ci.org/monero-project/monero) -[![Coverage Status](https://coveralls.io/repos/github/monero-project/monero/badge.svg?branch=master)](https://coveralls.io/github/monero-project/monero?branch=master) - ## Development Resources - Web: [getmonero.org](https://getmonero.org) - Forum: [forum.getmonero.org](https://forum.getmonero.org) - Mail: [dev@getmonero.org](mailto:dev@getmonero.org) - GitHub: [https://github.com/monero-project/monero](https://github.com/monero-project/monero) -- IRC: [#monero-dev on Freenode](irc://chat.freenode.net/#monero-dev) +- IRC: [#monero-dev on Freenode](http://webchat.freenode.net/?randomnick=1&channels=%23monero-dev&prompt=1&uio=d4) + +## Build + +| Operating System | Processor | Status | +| --------------------- | -------- |--------| +| Ubuntu 16.04 | i686 | [![Ubuntu 16.04 i686](https://build.getmonero.org/png?builder=monero-static-ubuntu-i686)](https://build.getmonero.org/builders/monero-static-ubuntu-i686) +| Ubuntu 16.04 | amd64 | [![Ubuntu 16.04 amd64](https://build.getmonero.org/png?builder=monero-static-ubuntu-amd64)](https://build.getmonero.org/builders/monero-static-ubuntu-amd64) +| Ubuntu 16.04 | armv7 | [![Ubuntu 16.04 armv7](https://build.getmonero.org/png?builder=monero-static-ubuntu-arm7)](https://build.getmonero.org/builders/monero-static-ubuntu-arm7) +| Debian Stable | armv8 | [![Debian armv8](https://build.getmonero.org/png?builder=monero-static-debian-armv8)](https://build.getmonero.org/builders/monero-static-debian-armv8) +| OSX 10.10 | amd64 | [![OSX 10.10 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.10)](https://build.getmonero.org/builders/monero-static-osx-10.10) +| OSX 10.11 | amd64 | [![OSX 10.11 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.11)](https://build.getmonero.org/builders/monero-static-osx-10.11) +| OSX 10.12 | amd64 | [![OSX 10.12 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.12)](https://build.getmonero.org/builders/monero-static-osx-10.12) +| FreeBSD 10.3 | amd64 | [![FreeBSD 10.3 amd64](https://build.getmonero.org/png?builder=monero-static-freebsd64)](https://build.getmonero.org/builders/monero-static-freebsd64) +| Windows (MSYS2/MinGW) | i686 | [![Windows (MSYS2/MinGW) i686](https://build.getmonero.org/png?builder=monero-static-win32)](https://build.getmonero.org/builders/monero-static-win32) +| Windows (MSYS2/MinGW) | amd64 | [![Windows (MSYS2/MinGW) amd64](https://build.getmonero.org/png?builder=monero-static-win64)](https://build.getmonero.org/builders/monero-static-win64) + +## Coverage + +| Type | Status | +|-----------|--------| +| Coverity | [![Coverity Status](https://scan.coverity.com/projects/9657/badge.svg)](https://scan.coverity.com/projects/9657/) +| Coveralls | [![Coveralls Status](https://coveralls.io/repos/github/monero-project/monero/badge.svg?branch=master)](https://coveralls.io/github/monero-project/monero?branch=master) +| License | [![License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) ## Introduction 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/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/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 b1049265d..12a04ee81 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3688,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()) @@ -3738,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); 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/wallet2.cpp b/src/wallet/wallet2.cpp index 5a59c6f7d..0c413546c 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(); \ @@ -701,6 +703,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; @@ -2352,6 +2365,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; @@ -2750,6 +2764,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 @@ -2759,7 +2807,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 @@ -3480,7 +3528,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 @@ -3762,7 +3810,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"); @@ -4068,7 +4116,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); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e17d4a5a4..04c4fc3a5 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -520,6 +520,8 @@ 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); @@ -570,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 { @@ -675,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> @@ -691,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); |