diff options
Diffstat (limited to 'src')
26 files changed, 1173 insertions, 173 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 4b6149cbd..d5d22bca6 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -32,7 +32,8 @@ set(common_sources dns_utils.cpp util.cpp i18n.cpp - perf_timer.cpp) + perf_timer.cpp + thread_group.cpp) if (STACK_TRACE) list(APPEND common_sources stack_trace.cpp) @@ -55,7 +56,8 @@ set(common_private_headers varint.h i18n.h perf_timer.h - stack_trace.h) + stack_trace.h + thread_group.h) monero_private_headers(common ${common_private_headers}) diff --git a/src/common/thread_group.cpp b/src/common/thread_group.cpp new file mode 100644 index 000000000..aa1b64f2e --- /dev/null +++ b/src/common/thread_group.cpp @@ -0,0 +1,164 @@ +// 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 "common/thread_group.h" + +#include <cassert> +#include <limits> +#include <stdexcept> + +#include "common/util.h" + +namespace tools +{ +thread_group::thread_group(std::size_t count) : internal() { + static_assert( + std::numeric_limits<unsigned>::max() <= std::numeric_limits<std::size_t>::max(), + "unexpected truncation" + ); + count = std::min<std::size_t>(count, get_max_concurrency()); + count = count ? count - 1 : 0; + + if (count) { + internal.emplace(count); + } +} + +thread_group::data::data(std::size_t count) + : threads() + , head{nullptr} + , last(std::addressof(head)) + , pending(count) + , mutex() + , has_work() + , finished_work() + , stop(false) { + threads.reserve(count); + while (count--) { + threads.push_back(std::thread(&thread_group::data::run, this)); + } +} + +thread_group::data::~data() noexcept { + { + const std::unique_lock<std::mutex> lock(mutex); + stop = true; + } + has_work.notify_all(); + finished_work.notify_all(); + for (auto& worker : threads) { + try { + worker.join(); + } + catch(...) {} + } +} + + +void thread_group::data::sync() noexcept { + /* This function and `run()` can both throw when acquiring the lock, or in + the dispatched function. It is tough to recover from either, particularly the + lock case. These functions are marked as noexcept so that if either call + throws, the entire process is terminated. Users of the `dispatch` call are + expected to make their functions noexcept, or use std::packaged_task to copy + exceptions so that the process will continue in all but the most pessimistic + cases (std::bad_alloc). This was the existing behavior; + `asio::io_service::run` propogates errors from dispatched calls, and uncaught + exceptions on threads result in process termination. */ + assert(!threads.empty()); + bool not_first = false; + while (true) { + std::unique_ptr<work> next = nullptr; + { + std::unique_lock<std::mutex> lock(mutex); + pending -= std::size_t(not_first); + not_first = true; + finished_work.notify_all(); + + if (stop) { + return; + } + + next = get_next(); + if (next == nullptr) { + finished_work.wait(lock, [this] { return pending == 0 || stop; }); + return; + } + } + assert(next->f); + next->f(); + } +} + +std::unique_ptr<thread_group::data::work> thread_group::data::get_next() noexcept { + std::unique_ptr<work> rc = std::move(head.ptr); + if (rc != nullptr) { + head.ptr = std::move(rc->next.ptr); + if (head.ptr == nullptr) { + last = std::addressof(head); + } + } + return rc; +} + +void thread_group::data::run() noexcept { + // see `sync()` source for additional information + while (true) { + std::unique_ptr<work> next = nullptr; + { + std::unique_lock<std::mutex> lock(mutex); + --pending; + finished_work.notify_all(); + has_work.wait(lock, [this] { return head.ptr != nullptr || stop; }); + if (stop) { + return; + } + next = get_next(); + } + assert(next != nullptr); + assert(next->f); + next->f(); + } +} + +void thread_group::data::dispatch(std::function<void()> f) { + std::unique_ptr<work> latest(new work{std::move(f), node{nullptr}}); + node* const latest_node = std::addressof(latest->next); + { + const std::unique_lock<std::mutex> lock(mutex); + assert(last != nullptr); + assert(last->ptr == nullptr); + if (pending == std::numeric_limits<std::size_t>::max()) { + throw std::overflow_error("thread_group exceeded max queue depth"); + } + last->ptr = std::move(latest); + last = latest_node; + ++pending; + } + has_work.notify_one(); +} +} diff --git a/src/common/thread_group.h b/src/common/thread_group.h new file mode 100644 index 000000000..d8461d49a --- /dev/null +++ b/src/common/thread_group.h @@ -0,0 +1,133 @@ +// 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 <boost/optional/optional.hpp> +#include <condition_variable> +#include <cstddef> +#include <functional> +#include <thread> +#include <utility> +#include <vector> + +namespace tools +{ +//! Manages zero or more threads for work dispatching +class thread_group +{ +public: + //! Create `min(count, get_max_concurrency()) - 1` threads + explicit thread_group(std::size_t count); + + thread_group(thread_group const&) = delete; + thread_group(thread_group&&) = delete; + + //! Joins threads, but does not necessarily run all dispatched functions. + ~thread_group() = default; + + thread_group& operator=(thread_group const&) = delete; + thread_group& operator=(thread_group&&) = delete; + + /*! Blocks until all functions provided to `dispatch` complete. Does not + destroy threads. If a dispatched function calls `this->dispatch(...)`, + `this->sync()` will continue to block until that new function completes. */ + void sync() noexcept { + if (internal) { + internal->sync(); + } + } + + /*! Example usage: + std::unique_ptr<thread_group, thread_group::lazy_sync> sync(std::addressof(group)); + which guarantees synchronization before the unique_ptr destructor returns. */ + struct lazy_sync { + void operator()(thread_group* group) const noexcept { + if (group != nullptr) { + group->sync(); + } + } + }; + + /*! `f` is invoked immediately if the thread_group is empty, otherwise + execution of `f` is queued for next available thread. If `f` is queued, any + exception leaving that function will result in process termination. Use + std::packaged_task if exceptions need to be handled. */ + template<typename F> + void dispatch(F&& f) { + if (internal) { + internal->dispatch(std::forward<F>(f)); + } + else { + f(); + } + } + +private: + class data { + public: + data(std::size_t count); + ~data() noexcept; + + void sync() noexcept; + + void dispatch(std::function<void()> f); + + private: + struct work; + + struct node { + node() = delete; + std::unique_ptr<work> ptr; + }; + + struct work { + work() = delete; + std::function<void()> f; + node next; + }; + + //! Requires lock on `mutex`. + std::unique_ptr<work> get_next() noexcept; + + //! Blocks until destructor is invoked, only call from thread. + void run() noexcept; + + private: + std::vector<std::thread> threads; + node head; + node* last; + std::size_t pending; + std::condition_variable has_work; + std::condition_variable finished_work; + std::mutex mutex; + bool stop; + }; + +private: + // optionally construct elements, without separate heap allocation + boost::optional<data> internal; +}; +} diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index b396fc7db..9ca835d9e 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -236,6 +236,6 @@ namespace crypto { } } -CRYPTO_MAKE_COMPARABLE(public_key) +CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 252b3d7f8..95cbf74f0 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 @@ -125,6 +127,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 6d64a43cb..234422e3d 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -876,6 +876,13 @@ namespace cryptonote return pk == out_key.key; } //--------------------------------------------------------------- + bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) + { + crypto::public_key pk; + derive_public_key(derivation, output_index, spend_public_key, pk); + return pk == out_key.key; + } + //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered) { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 24db8008e..d5dd6494d 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -115,6 +115,7 @@ namespace cryptonote bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); + bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); 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/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 677ba506a..65377f990 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -933,6 +933,7 @@ namespace cryptonote if(!m_core.find_blockchain_supplement(arg.block_ids, r)) { LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN."); + m_p2p->drop_connection(context); return 1; } LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size()); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 19e9d291e..df33c26b2 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -28,9 +28,9 @@ // 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 <boost/asio.hpp> #include "misc_log_ex.h" #include "common/perf_timer.h" +#include "common/thread_group.h" #include "common/util.h" #include "rctSigs.h" #include "cryptonote_core/cryptonote_format_utils.h" @@ -38,17 +38,22 @@ using namespace crypto; using namespace std; -#define KILL_IOSERVICE() \ - if(ioservice_active) \ - { \ - work.reset(); \ - while (!ioservice.stopped()) ioservice.poll(); \ - threadpool.join_all(); \ - ioservice.stop(); \ - ioservice_active = false; \ +namespace rct { + namespace { + struct verRangeWrapper_ { + void operator()(const key & C, const rangeSig & as, bool &result) const { + result = verRange(C, as); } + }; + constexpr const verRangeWrapper_ verRangeWrapper{}; -namespace rct { + struct verRctMGSimpleWrapper_ { + void operator()(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C, bool &result) const { + result = verRctMGSimple(message, mg, pubs, C); + } + }; + constexpr const verRctMGSimpleWrapper_ verRctMGSimpleWrapper{}; + } //Schnorr Non-linkable //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 @@ -360,10 +365,6 @@ namespace rct { return true; } - void verRangeWrapper(const key & C, const rangeSig & as, bool &result) { - result = verRange(C, as); - } - key get_pre_mlsag_hash(const rctSig &rv) { keyV hashes; @@ -544,9 +545,6 @@ namespace rct { return MLSAG_Ver(message, M, mg, rows); } - void verRctMGSimpleWrapper(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C, bool &result) { - result = verRctMGSimple(message, mg, pubs, C); - } //These functions get keys from blockchain //replace these when connecting blockchain @@ -767,38 +765,20 @@ namespace rct { // some rct ops can throw try { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(ioservice)); - size_t threads = tools::get_max_concurrency(); - 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 = 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(); }); + tools::thread_group threadpool(rv.outPk.size()); // this must destruct before results DP("range proofs verified?"); for (size_t i = 0; i < rv.outPk.size(); i++) { - if (threads > 1) { - ioservice.dispatch(boost::bind(&verRangeWrapper, std::cref(rv.outPk[i].mask), std::cref(rv.p.rangeSigs[i]), std::ref(results[i]))); - } - else { - bool tmp = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - DP(tmp); - if (!tmp) { - LOG_ERROR("Range proof verification failed for input " << i); - return false; - } - } + threadpool.dispatch( + std::bind(verRangeWrapper, std::cref(rv.outPk[i].mask), std::cref(rv.p.rangeSigs[i]), std::ref(results[i])) + ); } - KILL_IOSERVICE(); - if (threads > 1) { - for (size_t i = 0; i < rv.outPk.size(); ++i) { - if (!results[i]) { - LOG_ERROR("Range proof verified failed for input " << i); - return false; - } + threadpool.sync(); + for (size_t i = 0; i < rv.outPk.size(); ++i) { + if (!results[i]) { + LOG_ERROR("Range proof verified failed for input " << i); + return false; } } @@ -832,34 +812,23 @@ namespace rct { CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs"); CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); + const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); + tools::thread_group threadpool(threads); { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(ioservice)); - size_t threads = tools::get_max_concurrency(); - 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 = 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(); }); - - for (i = 0; i < rv.outPk.size(); i++) { - if (threads > 1) { - ioservice.dispatch(boost::bind(&verRangeWrapper, std::cref(rv.outPk[i].mask), std::cref(rv.p.rangeSigs[i]), std::ref(results[i]))); - } - else if (!verRange(rv.outPk[i].mask, rv.p.rangeSigs[i])) { - LOG_ERROR("Range proof verified failed for input " << i); - return false; + { + const std::unique_ptr<tools::thread_group, tools::thread_group::lazy_sync> + sync(std::addressof(threadpool)); + for (i = 0; i < rv.outPk.size(); i++) { + threadpool.dispatch( + std::bind(verRangeWrapper, std::cref(rv.outPk[i].mask), std::cref(rv.p.rangeSigs[i]), std::ref(results[i])) + ); } - } - KILL_IOSERVICE(); - if (threads > 1) { - for (size_t i = 0; i < rv.outPk.size(); ++i) { - if (!results[i]) { - LOG_ERROR("Range proof verified failed for input " << i); - return false; - } + } // threadpool.sync(); + for (size_t i = 0; i < rv.outPk.size(); ++i) { + if (!results[i]) { + LOG_ERROR("Range proof verified failed for input " << i); + return false; } } } @@ -875,37 +844,20 @@ namespace rct { key message = get_pre_mlsag_hash(rv); { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(ioservice)); - size_t threads = tools::get_max_concurrency(); - 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 = 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(); }); - - for (i = 0 ; i < rv.mixRing.size() ; i++) { - if (threads > 1) { - ioservice.dispatch(boost::bind(&verRctMGSimpleWrapper, std::cref(message), std::cref(rv.p.MGs[i]), std::cref(rv.mixRing[i]), std::cref(rv.pseudoOuts[i]), std::ref(results[i]))); + { + const std::unique_ptr<tools::thread_group, tools::thread_group::lazy_sync> + sync(std::addressof(threadpool)); + for (i = 0 ; i < rv.mixRing.size() ; i++) { + threadpool.dispatch( + std::bind(verRctMGSimpleWrapper, std::cref(message), std::cref(rv.p.MGs[i]), std::cref(rv.mixRing[i]), std::cref(rv.pseudoOuts[i]), std::ref(results[i])) + ); } - else { - bool tmpb = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); - DP(tmpb); - if (!tmpb) { - LOG_ERROR("verRctMGSimple failed for input " << i); - return false; - } - } - } - KILL_IOSERVICE(); - if (threads > 1) { - for (size_t i = 0; i < results.size(); ++i) { - if (!results[i]) { - LOG_ERROR("verRctMGSimple failed for input " << i); - return false; - } + } // threadpool.sync(); + for (size_t i = 0; i < results.size(); ++i) { + if (!results[i]) { + LOG_ERROR("verRctMGSimple failed for input " << i); + return false; } } } 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..ce1dac71a 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -77,8 +77,8 @@ 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" +#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\002" // workaround for a suspected bug in pthread/kernel on MacOS X #ifdef __APPLE__ @@ -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); @@ -4018,8 +4018,10 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) try { std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images(); - std::string data(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); + std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + + std::string data; 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)); for (const auto &i: ski) @@ -4027,7 +4029,10 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) data += std::string((const char *)&i.first, sizeof(crypto::key_image)); data += std::string((const char *)&i.second, sizeof(crypto::signature)); } - bool r = epee::file_io_utils::save_string_to_file(filename, data); + + // encrypt data, keep magic plaintext + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(data); + bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -4067,14 +4072,25 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) fail_msg_writer() << "Bad key image export file magic in " << filename; return true; } - const size_t headerlen = magiclen + 2 * sizeof(crypto::public_key); + + try + { + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); + return true; + } + + const size_t headerlen = 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 crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[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) { @@ -4133,11 +4149,13 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) boost::archive::binary_oarchive ar(oss); ar << outs; - std::string data(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + std::string magic(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()); + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); + bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -4177,14 +4195,25 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) fail_msg_writer() << "Bad output export file magic in " << filename; return true; } - const size_t headerlen = magiclen + 2 * sizeof(crypto::public_key); + + try + { + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); + return true; + } + + const size_t headerlen = 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 crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[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) { diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 26ce9fc7e..2521decea 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -69,11 +69,19 @@ string PendingTransactionImpl::errorString() const return m_errorString; } +std::vector<std::string> PendingTransactionImpl::txid() const +{ + std::vector<std::string> txid; + for (const auto &pt: m_pending_tx) + txid.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(pt.tx))); + return txid; +} + bool PendingTransactionImpl::commit() { - LOG_PRINT_L0("m_pending_tx size: " << m_pending_tx.size()); - assert(m_pending_tx.size() == 1); + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + try { while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); @@ -128,11 +136,16 @@ uint64_t PendingTransactionImpl::dust() const uint64_t PendingTransactionImpl::fee() const { uint64_t result = 0; - for (const auto ptx : m_pending_tx) { + for (const auto &ptx : m_pending_tx) { result += ptx.fee; } return result; } +uint64_t PendingTransactionImpl::txCount() const +{ + return m_pending_tx.size(); +} + } diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 8e09bec91..c5e847c97 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -49,6 +49,8 @@ public: uint64_t amount() const; uint64_t dust() const; uint64_t fee() const; + std::vector<std::string> txid() const; + uint64_t txCount() const; // TODO: continue with interface; private: 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.cpp b/src/wallet/api/wallet.cpp index 9a9638b40..6c1c1fea2 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -545,6 +545,8 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const { clearStatus(); + // Pause refresh thread while creating transaction + pauseRefresh(); vector<cryptonote::tx_destination_entry> dsts; cryptonote::tx_destination_entry de; @@ -678,6 +680,100 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const transaction->m_status = m_status; transaction->m_errorString = m_errorString; + // Resume refresh thread + startRefresh(); + return transaction; +} + +PendingTransaction *WalletImpl::createSweepUnmixableTransaction() + +{ + clearStatus(); + vector<cryptonote::tx_destination_entry> dsts; + cryptonote::tx_destination_entry de; + + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + do { + try { + transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(m_trustedDaemon); + + } catch (const tools::error::daemon_busy&) { + // TODO: make it translatable with "tr"? + m_errorString = tr("daemon is busy. Please try again later."); + m_status = Status_Error; + } catch (const tools::error::no_connection_to_daemon&) { + m_errorString = tr("no connection to daemon. Please make sure daemon is running."); + m_status = Status_Error; + } catch (const tools::error::wallet_rpc_error& e) { + m_errorString = tr("RPC error: ") + e.to_string(); + m_status = Status_Error; + } catch (const tools::error::get_random_outs_error&) { + m_errorString = tr("failed to get random outputs to mix"); + m_status = Status_Error; + + } catch (const tools::error::not_enough_money& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + + } catch (const tools::error::tx_not_possible& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee()); + m_errorString = writer.str(); + + } catch (const tools::error::not_enough_outs_to_mix& e) { + std::ostringstream writer; + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; + for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; + } + m_errorString = writer.str(); + m_status = Status_Error; + } catch (const tools::error::tx_not_constructed&) { + m_errorString = tr("transaction was not constructed"); + m_status = Status_Error; + } catch (const tools::error::tx_rejected& e) { + std::ostringstream writer; + writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + m_errorString = writer.str(); + m_status = Status_Error; + } catch (const tools::error::tx_sum_overflow& e) { + m_errorString = e.what(); + m_status = Status_Error; + } catch (const tools::error::zero_destination&) { + m_errorString = tr("one of destinations is zero"); + m_status = Status_Error; + } catch (const tools::error::tx_too_big& e) { + m_errorString = tr("failed to find a suitable way to split transactions"); + m_status = Status_Error; + } catch (const tools::error::transfer_error& e) { + m_errorString = string(tr("unknown transfer error: ")) + e.what(); + m_status = Status_Error; + } catch (const tools::error::wallet_internal_error& e) { + m_errorString = string(tr("internal error: ")) + e.what(); + m_status = Status_Error; + } catch (const std::exception& e) { + m_errorString = string(tr("unexpected error: ")) + e.what(); + m_status = Status_Error; + } catch (...) { + m_errorString = tr("unknown error"); + m_status = Status_Error; + } + } while (false); + + transaction->m_status = m_status; + transaction->m_errorString = m_errorString; return transaction; } @@ -707,6 +803,63 @@ void WalletImpl::setDefaultMixin(uint32_t arg) m_wallet->default_mixin(arg); } +bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + return false; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + m_wallet->set_tx_note(htxid, note); + return true; +} + +std::string WalletImpl::getUserNote(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + return ""; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + return m_wallet->get_tx_note(htxid); +} + +std::string WalletImpl::getTxKey(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + { + return ""; + } + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + crypto::secret_key tx_key; + if (m_wallet->get_tx_key(htxid, tx_key)) + { + return epee::string_tools::pod_to_hex(tx_key); + } + else + { + return ""; + } +} + +std::string WalletImpl::signMessage(const std::string &message) +{ + return m_wallet->sign(message); +} + +bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const +{ + cryptonote::account_public_address addr; + bool has_payment_id; + crypto::hash8 payment_id; + + if (!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), address)) + return false; + + return m_wallet->verify(message, addr, signature); +} bool WalletImpl::connectToDaemon() { @@ -720,9 +873,15 @@ bool WalletImpl::connectToDaemon() return result; } -bool WalletImpl::connected() const +Wallet::ConnectionStatus WalletImpl::connected() const { - return m_wallet->check_connection(); + bool same_version = false; + bool is_connected = m_wallet->check_connection(&same_version); + if (!is_connected) + return Wallet::ConnectionStatus_Disconnected; + if (!same_version) + return Wallet::ConnectionStatus_WrongVersion; + return Wallet::ConnectionStatus_Connected; } void WalletImpl::setTrustedDaemon(bool arg) @@ -798,6 +957,7 @@ void WalletImpl::doRefresh() void WalletImpl::startRefresh() { + LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed..."); if (!m_refreshEnabled) { m_refreshEnabled = true; m_refreshCV.notify_one(); @@ -818,6 +978,7 @@ void WalletImpl::stopRefresh() void WalletImpl::pauseRefresh() { + LOG_PRINT_L2(__FUNCTION__ << ": refresh paused..."); // TODO synchronize access if (!m_refreshThreadDone) { m_refreshEnabled = false; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index c8a59f7c3..f40551fac 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -70,7 +70,7 @@ public: bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit); void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit); bool connectToDaemon(); - bool connected() const; + ConnectionStatus connected() const; void setTrustedDaemon(bool arg); bool trustedDaemon() const; uint64_t balance() const; @@ -91,12 +91,19 @@ public: PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, uint64_t amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low); + virtual PendingTransaction * createSweepUnmixableTransaction(); virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; virtual void setListener(WalletListener * l); virtual uint32_t defaultMixin() const; virtual void setDefaultMixin(uint32_t arg); + virtual bool setUserNote(const std::string &txid, const std::string ¬e); + virtual std::string getUserNote(const std::string &txid) const; + virtual std::string getTxKey(const std::string &txid) const; + + virtual std::string signMessage(const std::string &message); + virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; private: void clearStatus(); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index d2395ace1..2d1c44d0e 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -31,6 +31,8 @@ #include "wallet_manager.h" #include "wallet.h" +#include "common_defines.h" +#include "net/http_client.h" #include <boost/filesystem.hpp> #include <boost/regex.hpp> @@ -133,6 +135,157 @@ void WalletManagerImpl::setDaemonHost(const std::string &hostname) } +bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const +{ + error = ""; + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data)) + { + error = tr("failed to parse txid"); + return false; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + if (txkey_text.size() < 64 || txkey_text.size() % 64) + { + error = tr("failed to parse tx key"); + return false; + } + crypto::secret_key tx_key; + cryptonote::blobdata tx_key_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data)) + { + error = tr("failed to parse tx key"); + return false; + } + tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); + + bool testnet = address_text[0] != '4'; + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id; + if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text)) + { + error = tr("failed to parse address"); + return false; + } + + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + epee::net_utils::http::http_simple_client http_client; + if (!epee::net_utils::invoke_http_json_remote_command2(daemon_address + "/gettransactions", req, res, http_client) || + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + { + error = tr("failed to get transaction from daemon"); + return false; + } + cryptonote::blobdata tx_data; + bool ok; + if (res.txs.size() == 1) + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) + { + error = tr("failed to parse transaction from daemon"); + return false; + } + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + { + error = tr("failed to validate transaction from daemon"); + return false; + } + if (tx_hash != txid) + { + error = tr("failed to get the right transaction from daemon"); + return false; + } + + crypto::key_derivation derivation; + if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + { + error = tr("failed to generate key derivation from supplied parameters"); + return false; + } + + received = 0; + try { + for (size_t n = 0; n < tx.vout.size(); ++n) + { + if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) + continue; + const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); + crypto::public_key pubkey; + derive_public_key(derivation, n, address.m_spend_public_key, pubkey); + if (pubkey == tx_out_to_key.key) + { + uint64_t amount; + if (tx.version == 1) + { + amount = tx.vout[n].amount; + } + else + { + try + { + rct::key Ctmp; + //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); + if (!r) + { + LOG_ERROR("Failed to generate key derivation to decode rct output " << n); + amount = 0; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + } + catch (...) { amount = 0; } + } + received += amount; + } + } + } + catch(const std::exception &e) + { + LOG_ERROR("error: " << e.what()); + error = std::string(tr("error: ")) + e.what(); + return false; + } + + if (received > 0) + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); + } + else + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid); + } + if (res.txs.front().in_pool) + { + height = 0; + } + else + { + height = res.txs.front().block_height; + } + + return true; +} ///////////////////// WalletManagerFactory implementation ////////////////////// diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 2e932a2a1..489abe764 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -46,6 +46,7 @@ public: std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; void setDaemonHost(const std::string &hostname); + bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; private: WalletManagerImpl() {} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8ea605375..ac8802ca4 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(); \ @@ -195,7 +197,7 @@ void wallet2::set_unspent(size_t idx) td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const +void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const { if (o.target.type() != typeid(txout_to_key)) { @@ -203,7 +205,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp LOG_ERROR("wrong type id in transaction out"); return; } - received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i); + received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i); if(received) { money_transfered = o.amount; // may be 0 for ringct outputs @@ -250,8 +252,6 @@ bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& a { 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; } //---------------------------------------------------------------------------------------------------- @@ -308,6 +308,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s std::deque<uint64_t> amount(tx.vout.size()); std::deque<rct::key> mask(tx.vout.size()); int threads = tools::get_max_concurrency(); + const cryptonote::account_keys& keys = m_account.get_keys(); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { // assume coinbase isn't for us @@ -316,7 +319,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, received, money_transfered, error); if (error) { r = false; @@ -326,14 +329,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s // this assumes that the miner tx pays a single address if (received) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + wallet_generate_key_image_helper(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"); outs.push_back(0); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]); } amount[0] = money_transfered; @@ -349,14 +351,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -369,14 +370,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(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"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -397,13 +397,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -417,14 +416,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(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"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -439,7 +437,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, received, money_transfered, error); if (error) { r = false; @@ -449,14 +447,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { if (received) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(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"); outs.push_back(i); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } amount[i] = money_transfered; @@ -484,12 +481,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); - auto kit = m_key_images.find(ki[o]); - THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(), - error::wallet_internal_error, std::string("Unexpected transfer index from key image: ") - + "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + auto kit = m_pub_keys.find(in_ephemeral[o].pub); + THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), + error::wallet_internal_error, std::string("Unexpected transfer index from public key: ") + + "got " + (kit == m_pub_keys.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size())); - if (kit == m_key_images.end()) + if (kit == m_pub_keys.end()) { if (!pool) { @@ -501,6 +498,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid(); td.m_key_image = ki[o]; + td.m_key_image_known = !m_watch_only; td.m_amount = tx.vout[o].amount; if (td.m_amount == 0) { @@ -520,6 +518,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; + m_pub_keys[in_ephemeral[o].pub] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) m_callback->on_money_received(height, tx, td.m_amount); @@ -527,14 +526,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(kit->first) + LOG_ERROR("Public key " << 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(kit->first) + LOG_ERROR("Public key " << 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 @@ -565,7 +564,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images"); + THROW_WALLET_EXCEPTION_IF(td.get_public_key() != in_ephemeral[o].pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); @@ -704,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; @@ -1334,7 +1344,13 @@ void wallet2::detach_blockchain(uint64_t height) auto it_ki = m_key_images.find(m_transfers[i].m_key_image); THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); m_key_images.erase(it_ki); - ++transfers_detached; + } + + for(size_t i = i_start; i!= m_transfers.size();i++) + { + auto it_pk = m_pub_keys.find(m_transfers[i].get_public_key()); + THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); + m_pub_keys.erase(it_pk); } m_transfers.erase(it, m_transfers.end()); @@ -1371,6 +1387,7 @@ bool wallet2::clear() m_blockchain.clear(); m_transfers.clear(); m_key_images.clear(); + m_pub_keys.clear(); m_unconfirmed_txs.clear(); m_payments.clear(); m_tx_keys.clear(); @@ -2147,13 +2164,11 @@ 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) + if (!td.m_key_image_known) continue; if (td.m_spent != (daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { @@ -2355,6 +2370,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; @@ -2753,6 +2769,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 @@ -2762,7 +2812,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 @@ -3483,7 +3533,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 @@ -3765,7 +3815,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"); @@ -4071,7 +4121,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); @@ -4264,10 +4314,7 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); - 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, + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && 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"); @@ -4323,7 +4370,10 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } for (size_t n = 0; n < signed_key_images.size(); ++n) + { m_transfers[n].m_key_image = signed_key_images[n].first; + m_transfers[n].m_key_image_known = true; + } m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/is_key_image_spent", req, daemon_resp, m_http_client, 200000); @@ -4389,6 +4439,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail "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); + td.m_key_image_known = true; 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); @@ -4398,6 +4449,61 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + std::string ciphertext; + crypto::chacha8_iv iv = crypto::rand<crypto::chacha8_iv>(); + ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); + crypto::chacha8(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + memcpy(&ciphertext[0], &iv, sizeof(iv)); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + crypto::signature &signature = *(crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, skey, signature); + } + return std::move(ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const +{ + return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const +{ + THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(chacha8_iv), + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + std::string plaintext; + plaintext.resize(ciphertext.size() - sizeof(iv) - (authenticated ? sizeof(crypto::signature) : 0)); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), + error::wallet_internal_error, "Failed to authenticate criphertext"); + } + crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - sizeof(iv), key, iv, &plaintext[0]); + return std::move(plaintext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const +{ + return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6cd288ac1..3c4b1015f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -109,9 +109,11 @@ namespace tools rct::key m_mask; uint64_t m_amount; bool m_rct; + bool m_key_image_known; bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } + const crypto::public_key &get_public_key() const { return boost::get<const cryptonote::txout_to_key>(m_tx.vout[m_internal_output_index].target).key; } }; struct payment_details @@ -409,6 +411,19 @@ namespace tools a & m_unconfirmed_payments; if(ver < 14) return; + if(ver < 15) + { + // we're loading an older wallet without a pubkey map, rebuild it + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; + const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); + m_pub_keys.emplace(o.key, i); + } + return; + } + a & m_pub_keys; } /*! @@ -480,6 +495,12 @@ namespace tools uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent); void update_pool_state(); + + std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; + std::string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -515,11 +536,13 @@ namespace tools void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; + void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; 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); @@ -543,6 +566,7 @@ namespace tools transfer_container m_transfers; payment_container m_payments; std::unordered_map<crypto::key_image, size_t> m_key_images; + std::unordered_map<crypto::public_key, size_t> m_pub_keys; cryptonote::account_public_address m_account_public_address; std::unordered_map<crypto::hash, std::string> m_tx_notes; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value @@ -567,11 +591,11 @@ namespace tools bool m_confirm_missing_payment_id; }; } -BOOST_CLASS_VERSION(tools::wallet2, 14) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 4) +BOOST_CLASS_VERSION(tools::wallet2, 15) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 5) 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 { @@ -597,6 +621,7 @@ namespace boost { x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; } + x.m_key_image_known = true; } template <class Archive> @@ -644,6 +669,9 @@ namespace boost return; } a & x.m_rct; + if (ver < 5) + return; + a & x.m_key_image_known; } template <class Archive> @@ -675,6 +703,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 +727,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/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8427ba250..f0a9ea68b 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -65,6 +65,12 @@ struct PendingTransaction virtual uint64_t amount() const = 0; virtual uint64_t dust() const = 0; virtual uint64_t fee() const = 0; + virtual std::vector<std::string> txid() const = 0; + /*! + * \brief txCount - number of transactions current transaction will be splitted to + * \return + */ + virtual uint64_t txCount() const = 0; }; /** @@ -159,6 +165,12 @@ struct Wallet Status_Error }; + enum ConnectionStatus { + ConnectionStatus_Disconnected, + ConnectionStatus_Connected, + ConnectionStatus_WrongVersion + }; + virtual ~Wallet() = 0; virtual std::string seed() const = 0; virtual std::string getSeedLanguage() const = 0; @@ -243,7 +255,7 @@ struct Wallet * @brief connected - checks if the wallet connected to the daemon * @return - true if connected */ - virtual bool connected() const = 0; + virtual ConnectionStatus connected() const = 0; virtual void setTrustedDaemon(bool arg) = 0; virtual bool trustedDaemon() const = 0; virtual uint64_t balance() const = 0; @@ -324,6 +336,14 @@ struct Wallet PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0; /*! + * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. + * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() + * after object returned + */ + + virtual PendingTransaction * createSweepUnmixableTransaction() = 0; + + /*! * \brief disposeTransaction - destroys transaction object * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; */ @@ -340,6 +360,36 @@ struct Wallet * \param arg */ virtual void setDefaultMixin(uint32_t arg) = 0; + + /*! + * \brief setUserNote - attach an arbitrary string note to a txid + * \param txid - the transaction id to attach the note to + * \param note - the note + * \return true if succesful, false otherwise + */ + virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; + /*! + * \brief getUserNote - return an arbitrary string note attached to a txid + * \param txid - the transaction id to attach the note to + * \return the attached note, or empty string if there is none + */ + virtual std::string getUserNote(const std::string &txid) const = 0; + virtual std::string getTxKey(const std::string &txid) const = 0; + + /* + * \brief signMessage - sign a message with the spend private key + * \param message - the message to sign (arbitrary byte data) + * \return the signature + */ + virtual std::string signMessage(const std::string &message) = 0; + /*! + * \brief verifySignedMessage - verify a signature matches a given message + * \param message - the message (arbitrary byte data) + * \param address - the address the signature claims to be made with + * \param signature - the signature + * \return true if the signature verified, false otherwise + */ + virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; }; /** @@ -400,6 +450,19 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; + /*! + * \brief checkPayment - checks a payment was made using a txkey + * \param address - the address the payment was sent to + * \param txid - the transaction id for that payment + * \param txkey - the transaction's secret key + * \param daemon_address - the address (host and port) to the daemon to request transaction data + * \param received - if succesful, will hold the amount of monero received + * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) + * \param error - if unsuccesful, will hold an error string with more information about the error + * \return - true is succesful, false otherwise + */ + virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; + //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; |