diff options
45 files changed, 743 insertions, 330 deletions
diff --git a/.gitignore b/.gitignore index 622bbdb3a..a27982af1 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,4 @@ local.properties .texlipse .idea/ +/testnet
\ No newline at end of file @@ -57,6 +57,20 @@ There are also several mining pools that kindly donate a portion of their fees, See [LICENSE](LICENSE). +## Monero software updates and consensus protocol changes (hard forking) + +Monero uses a hardforking mechanism to implement new features which requires that +users of Monero software run current versions and update their software on a +regular schedule. Here is the current schedule, versions, and compatability. +Dates are provided in the format YYYYMMDD. + + +| Date | Consensus version | Minimum Monero Version | Recommended Monero Version | Details | +| ----------------- | ----------------- | ---------------------- | -------------------------- | ------------------ | +| 2016-09-21 | v3 | v0.9.4 | v0.10.0 | Splits coinbase into denominations | +| 2017-01-05 | v4 | v0.9.4 | v0.10.0 | Allow normal and RingCT transactions | +| 2017-09-21 | v5 | v0.10.0 | v0.10.0 | Allow only RingCT transactions | + ## Installing Monero from a Package Packages are available for diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 5b6a793d8..91c388de6 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1309,10 +1309,11 @@ public: * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict count to unlocked outputs + * @param recent_cutoff timestamp to determine whether an output is recent * * @return a set of amount/instances */ - virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const = 0; + virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const = 0; /** * @brief is BlockchainDB in read-only mode? diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index acb7d2cf6..b5459b56b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2657,7 +2657,7 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std:: LOG_PRINT_L3("db3: " << db3); } -std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const +std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -2665,7 +2665,7 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec TXN_PREFIX_RDONLY(); RCURSOR(output_amounts); - std::map<uint64_t, uint64_t> histogram; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; MDB_val k; MDB_val v; @@ -2683,7 +2683,7 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec mdb_size_t num_elems = 0; mdb_cursor_count(m_cur_output_amounts, &num_elems); uint64_t amount = *(const uint64_t*)k.mv_data; - histogram[amount] = num_elems; + histogram[amount] = std::make_tuple(num_elems, 0, 0); } } else @@ -2694,13 +2694,13 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET); if (ret == MDB_NOTFOUND) { - histogram[amount] = 0; + histogram[amount] = std::make_tuple(0, 0, 0); } else if (ret == MDB_SUCCESS) { mdb_size_t num_elems = 0; mdb_cursor_count(m_cur_output_amounts, &num_elems); - histogram[amount] = num_elems; + histogram[amount] = std::make_tuple(num_elems, 0, 0); } else { @@ -2709,11 +2709,11 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec } } - if (unlocked) { + if (unlocked || recent_cutoff > 0) { const uint64_t blockchain_height = height(); - for (auto i: histogram) { - uint64_t amount = i.first; - uint64_t num_elems = i.second; + for (std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>::iterator i = histogram.begin(); i != histogram.end(); ++i) { + uint64_t amount = i->first; + uint64_t num_elems = std::get<0>(i->second); while (num_elems > 0) { const tx_out_index toi = get_output_tx_and_index(amount, num_elems - 1); const uint64_t height = get_tx_block_height(toi.first); @@ -2722,7 +2722,23 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec --num_elems; } // modifying second does not invalidate the iterator - i.second = num_elems; + std::get<1>(i->second) = num_elems; + + if (recent_cutoff > 0) + { + uint64_t recent = 0; + while (num_elems > 0) { + const tx_out_index toi = get_output_tx_and_index(amount, num_elems - 1); + const uint64_t height = get_tx_block_height(toi.first); + const uint64_t ts = get_block_timestamp(height); + if (ts < recent_cutoff) + break; + --num_elems; + ++recent; + } + // modifying second does not invalidate the iterator + std::get<2>(i->second) = recent; + } } } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 9df4b86d1..6db5abca1 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -265,10 +265,11 @@ public: * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict count to unlocked outputs + * @param recent_cutoff timestamp to determine which outputs are recent * * @return a set of amount/instances */ - std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const; private: void do_resize(uint64_t size_increase=0); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6bf8b1777..4b6149cbd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -31,7 +31,8 @@ set(common_sources command_line.cpp dns_utils.cpp util.cpp - i18n.cpp) + i18n.cpp + perf_timer.cpp) if (STACK_TRACE) list(APPEND common_sources stack_trace.cpp) @@ -53,6 +54,7 @@ set(common_private_headers util.h varint.h i18n.h + perf_timer.h stack_trace.h) monero_private_headers(common diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp new file mode 100644 index 000000000..d23c9f11d --- /dev/null +++ b/src/common/perf_timer.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 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 "perf_timer.h" + +namespace tools +{ + +int performance_timer_log_level = 2; +__thread std::vector<PerformanceTimer*> *performance_timers = NULL; + +void set_performance_timer_log_level(int level) +{ + if (level < LOG_LEVEL_MIN || level > LOG_LEVEL_MAX) + { + LOG_PRINT_L0("Wrong log level: " << level << ", using 2"); + level = 2; + } + performance_timer_log_level = level; +} + +} diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h new file mode 100644 index 000000000..3d732012d --- /dev/null +++ b/src/common/perf_timer.h @@ -0,0 +1,92 @@ +// Copyright (c) 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. + +#pragma once + +#include <string> +#include <stdio.h> +#include "misc_log_ex.h" + +namespace tools +{ + +class PerformanceTimer; + +extern int performance_timer_log_level; +extern __thread std::vector<PerformanceTimer*> *performance_timers; + +class PerformanceTimer +{ +public: + PerformanceTimer(const std::string &s, int l = LOG_LEVEL_2): name(s), level(l), started(false) + { + ticks = epee::misc_utils::get_tick_count(); + if (!performance_timers) + { + LOG_PRINT("PERF ----------", level); + performance_timers = new std::vector<PerformanceTimer*>(); + } + else + { + PerformanceTimer *pt = performance_timers->back(); + if (!pt->started) + { + LOG_PRINT("PERF " << std::string((performance_timers->size()-1) * 2, ' ') << " " << pt->name, pt->level); + pt->started = true; + } + } + performance_timers->push_back(this); + } + + ~PerformanceTimer() + { + performance_timers->pop_back(); + ticks = epee::misc_utils::get_tick_count() - ticks; + char s[12]; + snprintf(s, sizeof(s), "%8lu ", ticks); + LOG_PRINT("PERF " << s << std::string(performance_timers->size() * 2, ' ') << " " << name, level); + if (performance_timers->empty()) + { + delete performance_timers; + performance_timers = NULL; + } + } + +private: + std::string name; + int level; + uint64_t ticks; + bool started; +}; + +void set_performance_timer_log_level(int level); + +#define PERF_TIMER(name) tools::PerformanceTimer pt_##name(#name, tools::performance_timer_log_level) +#define PERF_TIMER_L(name, l) tools::PerformanceTimer pt_##name(#name, l) + +} diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 83290796b..9ea023a4c 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -52,6 +52,7 @@ #include "cryptonote_core/checkpoints.h" #include "cryptonote_core/cryptonote_core.h" #include "ringct/rctSigs.h" +#include "common/perf_timer.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" #endif @@ -2245,6 +2246,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) { + PERF_TIMER(expand_transaction_2); CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); rct::rctSig &rv = tx.rct_signatures; @@ -2321,6 +2323,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr // using threads, etc.) bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) { + PERF_TIMER(check_tx_inputs); LOG_PRINT_L3("Blockchain::" << __func__); size_t sig_index = 0; if(pmax_used_block_height) @@ -3754,9 +3757,9 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } -std::map<uint64_t, uint64_t> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const +std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { - return m_db->get_output_histogram(amounts, unlocked); + return m_db->get_output_histogram(amounts, unlocked, recent_cutoff); } #if defined(PER_BLOCK_CHECKPOINT) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 94701608e..262c2952b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -729,10 +729,11 @@ namespace cryptonote * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict instances to unlocked ones + * @param recent_cutoff timestamp to consider outputs as recent * * @return a set of amount/instances */ - std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const; /** * @brief perform a check on all key images in the blockchain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 149fb09df..a82d96416 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -616,6 +616,34 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) + { + std::list<block> blocks; + std::list<transaction> txs; + std::list<crypto::hash> missed_txs; + uint64_t coinbase_amount = 0; + uint64_t emission_amount = 0; + uint64_t total_fee_amount = 0; + uint64_t tx_fee_amount = 0; + this->get_blocks(start_offset, count, blocks); + BOOST_FOREACH(auto& b, blocks) + { + coinbase_amount = get_outs_money_amount(b.miner_tx); + this->get_transactions(b.tx_hashes, txs, missed_txs); + BOOST_FOREACH(const auto& tx, txs) + { + tx_fee_amount += get_tx_fee(tx); + } + + emission_amount += coinbase_amount - tx_fee_amount; + total_fee_amount += tx_fee_amount; + coinbase_amount = 0; + tx_fee_amount = 0; + } + + return std::pair<uint64_t, uint64_t>(emission_amount, total_fee_amount); + } + //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const { std::unordered_set<crypto::key_image> ki; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 6727d6b04..d925a184d 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -600,6 +600,13 @@ namespace cryptonote */ size_t get_block_sync_size() const { return block_sync_size; } + /** + * @brief get the sum of coinbase tx amounts between blocks + * + * @return the number of blocks to sync in one go + */ + std::pair<uint64_t, uint64_t> get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); + private: /** diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 64f8eb924..870e8f0d8 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -658,10 +658,7 @@ namespace cryptonote } else { - bool all_rct_inputs = true; size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - all_rct_inputs &= !(src_entr.mask == rct::identity()); // the non-simple version is slightly smaller, but assumes all real inputs // are on the same index, so can only be used if there just one ring. diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 5bfa7eca6..178cbda3c 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -42,6 +42,7 @@ #include "common/int-util.h" #include "misc_language.h" #include "warnings.h" +#include "common/perf_timer.h" #include "crypto/hash.h" DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated @@ -78,6 +79,7 @@ namespace cryptonote //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version) { + PERF_TIMER(add_tx); if (tx.version == 0) { // v0 never accepted diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 6ea862b56..5d7ed6cc0 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -452,5 +452,27 @@ bool t_command_parser_executor::output_histogram(const std::vector<std::string>& return m_executor.output_histogram(min_count, max_count); } +bool t_command_parser_executor::print_coinbase_tx_sum(const std::vector<std::string>& args) +{ + if(!args.size()) + { + std::cout << "need block height parameter" << std::endl; + return false; + } + uint64_t height = 0; + uint64_t count = 0; + if(!epee::string_tools::get_xtype_from_string(height, args[0])) + { + std::cout << "wrong starter block height parameter" << std::endl; + return false; + } + if(args.size() >1 && !epee::string_tools::get_xtype_from_string(count, args[1])) + { + std::cout << "wrong count parameter" << std::endl; + return false; + } + + return m_executor.print_coinbase_tx_sum(height, count); +} } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 7819bd261..6a984aa71 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -115,6 +115,8 @@ public: bool flush_txpool(const std::vector<std::string>& args); bool output_histogram(const std::vector<std::string>& args); + + bool print_coinbase_tx_sum(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index cb54d1966..1418b920f 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -215,6 +215,11 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1) , "Print output histogram (amount, instances)" ); + m_command_lookup.set_handler( + "print_coinbase_tx_sum" + , std::bind(&t_command_parser_executor::print_coinbase_tx_sum, &m_parser, p::_1) + , "Print sum of coinbase transactions (start height, block count)" + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index e7229f7f9..bed10715b 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1243,6 +1243,8 @@ bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_c req.min_count = min_count; req.max_count = max_count; + req.unlocked = false; + req.recent_cutoff = 0; if (m_is_rpc) { @@ -1261,14 +1263,49 @@ bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_c } std::sort(res.histogram.begin(), res.histogram.end(), - [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.instances < e2.instances; }); + [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.total_instances < e2.total_instances; }); for (const auto &e: res.histogram) { - tools::msg_writer() << e.instances << " " << cryptonote::print_money(e.amount); + tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); } return true; } +bool t_rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count) +{ + cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::request req; + cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::response res; + epee::json_rpc::error error_resp; + + req.height = height; + req.count = count; + + std::string fail_message = "Unsuccessful"; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "get_coinbase_tx_sum", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_coinbase_tx_sum(req, res, error_resp)) + { + tools::fail_msg_writer() << fail_message.c_str(); + return true; + } + } + + tools::msg_writer() << "Sum of coinbase transactions between block heights [" + << height << ", " << (height + count) << ") is " + << cryptonote::print_money(res.emission_amount + res.fee_amount) << " " + << "consisting of " << cryptonote::print_money(res.emission_amount) + << " in emissions, and " << cryptonote::print_money(res.fee_amount) << " in fees"; + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 5eed44353..c097453e7 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -133,6 +133,8 @@ public: bool flush_txpool(const std::string &txid); bool output_histogram(uint64_t min_count, uint64_t max_count); + + bool print_coinbase_tx_sum(uint64_t height, uint64_t count); }; } // namespace daemonize diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index ed1f8cc0e..80ece9c44 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -29,6 +29,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "misc_log_ex.h" +#include "common/perf_timer.h" #include "rctSigs.h" #include "cryptonote_core/cryptonote_format_utils.h" @@ -107,6 +108,7 @@ namespace rct { // an x[i] such that x[i]G = one of P1[i] or P2[i] // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i bool VerASNL(const key64 P1, const key64 P2, const asnlSig &as) { + PERF_TIMER(VerASNL); DP("Verifying Aggregate Schnorr Non-linkable Ring Signature\n"); key LHS = identity(); key RHS = scalarmultBase(as.s); @@ -150,7 +152,7 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -239,7 +241,7 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - bool MLSAG_Ver(key message, const keyM & pk, const mgSig & rv, size_t dsRows) { + bool MLSAG_Ver(const key &message, const keyM & pk, const mgSig & rv, size_t dsRows) { size_t cols = pk.size(); CHECK_AND_ASSERT_MES(cols >= 2, false, "Error! What is c if cols = 1!"); @@ -331,6 +333,7 @@ namespace rct { // mask is a such that C = aG + bH, and b = amount //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i bool verRange(const key & C, const rangeSig & as) { + PERF_TIMER(verRange); key64 CiH; int i = 0; key Ctmp = identity(); @@ -467,6 +470,7 @@ namespace rct { //Ver: // verifies the above sig is created corretly bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFeeKey, const key &message) { + PERF_TIMER(verRctMG); //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); @@ -505,6 +509,7 @@ namespace rct { //This does a simplified version, assuming only post Rct //inputs bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C) { + PERF_TIMER(verRctMGSimple); //setup vars size_t rows = 1; size_t cols = pubs.size(); @@ -729,6 +734,7 @@ namespace rct { // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number bool verRct(const rctSig & rv) { + PERF_TIMER(verRct); CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "verRct called on non-full rctSig"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); @@ -769,6 +775,7 @@ namespace rct { //ver RingCT simple //assumes only post-rct style inputs (at least for max anonymity) bool verRctSimple(const rctSig & rv) { + PERF_TIMER(verRctSimple); size_t i = 0; CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "verRctSimple called on non simple rctSig"); diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index f1c906d5e..11d771818 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -90,8 +90,8 @@ namespace rct { // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly keyV keyImageV(const keyV &xx); - mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); - bool MLSAG_Ver(key message, const keyM &pk, const mgSig &sig, size_t dsRows); + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); + bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); //proveRange and verRange diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index bfafebb83..25f6f9bc9 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -415,7 +415,7 @@ namespace rct { // then the value in the first 8 bytes is returned xmr_amount h2d(const key &test); //32 byte key to int[64] - void h2b(bits amountb2, key & test); + void h2b(bits amountb2, const key & test); //int[64] to 32 byte key void b2h(key & amountdh, bits amountb2); //int[64] to uint long long diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 700019250..f5258268c 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1236,10 +1236,10 @@ namespace cryptonote return false; } - std::map<uint64_t, uint64_t> histogram; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; try { - histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked); + histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked, req.recent_cutoff); } catch (const std::exception &e) { @@ -1251,8 +1251,8 @@ namespace cryptonote res.histogram.reserve(histogram.size()); for (const auto &i: histogram) { - if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0)) - res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second)); + if (std::get<0>(i.second) >= req.min_count && (std::get<0>(i.second) <= req.max_count || req.max_count == 0)) + res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, std::get<0>(i.second), std::get<1>(i.second), std::get<2>(i.second))); } res.status = CORE_RPC_STATUS_OK; @@ -1266,6 +1266,14 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + std::pair<uint64_t, uint64_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); + res.emission_amount = amounts.first; + res.fee_amount = amounts.second; + 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 68da59f6b..147f019d6 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -115,6 +115,7 @@ namespace cryptonote MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted) 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) END_JSON_RPC_MAP() END_URI_MAP2() @@ -160,6 +161,7 @@ namespace cryptonote bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp); 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); //----------------------- private: diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index dd2116e51..135cf1fe7 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1172,26 +1172,33 @@ namespace cryptonote uint64_t min_count; uint64_t max_count; bool unlocked; + uint64_t recent_cutoff; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amounts); KV_SERIALIZE(min_count); KV_SERIALIZE(max_count); KV_SERIALIZE(unlocked); + KV_SERIALIZE(recent_cutoff); END_KV_SERIALIZE_MAP() }; struct entry { uint64_t amount; - uint64_t instances; + uint64_t total_instances; + uint64_t unlocked_instances; + uint64_t recent_instances; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount); - KV_SERIALIZE(instances); + KV_SERIALIZE(total_instances); + KV_SERIALIZE(unlocked_instances); + KV_SERIALIZE(recent_instances); END_KV_SERIALIZE_MAP() - entry(uint64_t amount, uint64_t instances): amount(amount), instances(instances) {} + entry(uint64_t amount, uint64_t total_instances, uint64_t unlocked_instances, uint64_t recent_instances): + amount(amount), total_instances(total_instances), unlocked_instances(unlocked_instances), recent_instances(recent_instances) {} entry() {} }; @@ -1226,5 +1233,31 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; -} + struct COMMAND_RPC_GET_COINBASE_TX_SUM + { + struct request + { + uint64_t height; + uint64_t count; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height); + KV_SERIALIZE(count); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t emission_amount; + uint64_t fee_amount; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(emission_amount) + KV_SERIALIZE(fee_amount) + END_KV_SERIALIZE_MAP() + }; + }; +} diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index eced4537f..639fcb4b6 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -678,7 +678,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer_original, but using a new transaction building algorithm")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [<mixin_count>] <addr> <amount> <lockblocks>(Number of blocks to lock the transaction for, max 1000000) [<payment_id>]")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); - m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); + m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [mixin] address [payment_id] - Send all unlocked balance an address")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 95aafb04f..e4a003b02 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -60,21 +60,44 @@ TransactionHistoryImpl::~TransactionHistoryImpl() int TransactionHistoryImpl::count() const { - return m_history.size(); + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + int result = m_history.size(); + return result; +} + +TransactionInfo *TransactionHistoryImpl::transaction(int index) const +{ + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + // sanity check + if (index < 0) + return nullptr; + unsigned index_ = static_cast<unsigned>(index); + return index_ < m_history.size() ? m_history[index_] : nullptr; } TransactionInfo *TransactionHistoryImpl::transaction(const std::string &id) const { - return nullptr; + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + auto itr = std::find_if(m_history.begin(), m_history.end(), + [&](const TransactionInfo * ti) { + return ti->hash() == id; + }); + return itr != m_history.end() ? *itr : nullptr; } std::vector<TransactionInfo *> TransactionHistoryImpl::getAll() const { + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); return m_history; } void TransactionHistoryImpl::refresh() { + // multithreaded access: + // boost::lock_guard<boost::mutex> guarg(m_historyMutex); + // for "write" access, locking exclusively + boost::unique_lock<boost::shared_mutex> lock(m_historyMutex); + // TODO: configurable values; uint64_t min_height = 0; uint64_t max_height = (uint64_t)-1; @@ -84,8 +107,6 @@ void TransactionHistoryImpl::refresh() delete t; m_history.clear(); - - // transactions are stored in wallet2: // - confirmed_transfer_details - out transfers // - unconfirmed_transfer_details - pending out transfers @@ -109,7 +130,7 @@ void TransactionHistoryImpl::refresh() ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; // TODO: - // ti->m_timestamp = pd.m_timestamp; + ti->m_timestamp = pd.m_timestamp; m_history.push_back(ti); /* output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s") @@ -151,6 +172,7 @@ void TransactionHistoryImpl::refresh() ti->m_direction = TransactionInfo::Direction_Out; ti->m_hash = string_tools::pod_to_hex(hash); ti->m_blockheight = pd.m_block_height; + ti->m_timestamp = pd.m_timestamp; // single output transaction might contain multiple transfers for (const auto &d: pd.m_dests) { @@ -180,14 +202,9 @@ void TransactionHistoryImpl::refresh() ti->m_failed = is_failed; ti->m_pending = true; ti->m_hash = string_tools::pod_to_hex(hash); + ti->m_timestamp = pd.m_timestamp; m_history.push_back(ti); } - -} - -TransactionInfo *TransactionHistoryImpl::transaction(int index) const -{ - return nullptr; } -} +} // namespace diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 171fd2210..1b6617ead 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "wallet/wallet2_api.h" +#include <boost/thread/shared_mutex.hpp> namespace Bitmonero { @@ -51,6 +52,7 @@ private: // TransactionHistory is responsible of memory management std::vector<TransactionInfo*> m_history; WalletImpl *m_wallet; + mutable boost::shared_mutex m_historyMutex; }; } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 4d35bc404..d1c849537 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -54,8 +54,9 @@ namespace { struct Wallet2CallbackImpl : public tools::i_wallet2_callback { - Wallet2CallbackImpl() + Wallet2CallbackImpl(WalletImpl * wallet) : m_listener(nullptr) + , m_wallet(wallet) { } @@ -93,7 +94,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount)); - if (m_listener) { + // do not signal on received tx if wallet is not syncronized completely + if (m_listener && m_wallet->synchronized()) { m_listener->moneyReceived(tx_hash, amount); m_listener->updated(); } @@ -107,7 +109,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount)); - if (m_listener) { + // do not signal on sent tx if wallet is not syncronized completely + if (m_listener && m_wallet->synchronized()) { m_listener->moneySpent(tx_hash, amount); m_listener->updated(); } @@ -119,6 +122,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } WalletListener * m_listener; + WalletImpl * m_wallet; }; Wallet::~Wallet() {} @@ -166,12 +170,16 @@ uint64_t Wallet::maximumAllowedAmount() ///////////////////////// WalletImpl implementation //////////////////////// WalletImpl::WalletImpl(bool testnet) - :m_wallet(nullptr), m_status(Wallet::Status_Ok), m_trustedDaemon(false), - m_wallet2Callback(nullptr), m_recoveringFromSeed(false) + :m_wallet(nullptr) + , m_status(Wallet::Status_Ok) + , m_trustedDaemon(false) + , m_wallet2Callback(nullptr) + , m_recoveringFromSeed(false) + , m_synchronized(false) { m_wallet = new tools::wallet2(testnet); m_history = new TransactionHistoryImpl(this); - m_wallet2Callback = new Wallet2CallbackImpl; + m_wallet2Callback = new Wallet2CallbackImpl(this); m_wallet->callback(m_wallet2Callback); m_refreshThreadDone = false; m_refreshEnabled = false; @@ -201,7 +209,6 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co bool keys_file_exists; bool wallet_file_exists; tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); - // TODO: figure out how to setup logger; LOG_PRINT_L3("wallet_path: " << path << ""); LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); @@ -404,7 +411,15 @@ void WalletImpl::initAsync(const string &daemon_address, uint64_t upper_transact startRefresh(); } +void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) +{ + m_wallet->set_refresh_from_block_height(refresh_from_block_height); +} +void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed) +{ + m_recoveringFromSeed = recoveringFromSeed; +} uint64_t WalletImpl::balance() const { @@ -455,6 +470,11 @@ uint64_t WalletImpl::daemonBlockChainTargetHeight() const return result; } +bool WalletImpl::synchronized() const +{ + return m_synchronized; +} + bool WalletImpl::refresh() { clearStatus(); @@ -723,6 +743,15 @@ void WalletImpl::doRefresh() boost::lock_guard<boost::mutex> guarg(m_refreshMutex2); try { m_wallet->refresh(); + if (!m_synchronized) { + m_synchronized = true; + } + // assuming if we have empty history, it wasn't initialized yet + // for futher history changes client need to update history in + // "on_money_received" and "on_money_sent" callbacks + if (m_history->count() == 0) { + m_history->refresh(); + } } catch (const std::exception &e) { m_status = Status_Error; m_errorString = e.what(); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index c399e3ab6..c8a59f7c3 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -78,10 +78,13 @@ public: uint64_t blockChainHeight() const; uint64_t daemonBlockChainHeight() const; uint64_t daemonBlockChainTargetHeight() const; + bool synchronized() const; bool refresh(); void refreshAsync(); void setAutoRefreshInterval(int millis); int autoRefreshInterval() const; + void setRefreshFromBlockHeight(uint64_t refresh_from_block_height); + void setRecoveringFromSeed(bool recoveringFromSeed); @@ -108,6 +111,7 @@ private: private: friend class PendingTransactionImpl; friend class TransactionHistoryImpl; + friend class Wallet2CallbackImpl; tools::wallet2 * m_wallet; mutable std::atomic<int> m_status; @@ -133,6 +137,7 @@ private: // so it shouldn't be considered as new and pull blocks (slow-refresh) // instead of pulling hashes (fast-refresh) bool m_recoveringFromSeed; + std::atomic<bool> m_synchronized; }; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index ca83806ff..aa99476ee 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -57,9 +57,12 @@ Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string return wallet; } -Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &memo, bool testnet) +Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &memo, bool testnet, uint64_t restoreHeight) { WalletImpl * wallet = new WalletImpl(testnet); + if(restoreHeight > 0){ + wallet->setRefreshFromBlockHeight(restoreHeight); + } wallet->recover(path, memo); return wallet; } diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 7585c1af7..2e932a2a1 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -40,7 +40,7 @@ public: Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet); Wallet * openWallet(const std::string &path, const std::string &password, bool testnet); - virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet); + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet, uint64_t restoreHeight); virtual bool closeWallet(Wallet *wallet); bool walletExists(const std::string &path); std::vector<std::string> findWallets(const std::string &path); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e3736bc3d..6a60ebe28 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -77,6 +77,9 @@ using namespace cryptonote; #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\001" #define SIGNED_TX_PREFIX "Monero signed tx set\001" +#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 KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -583,11 +586,14 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s std::string(", expected ") + print_money(td.amount())); } amount = td.amount(); - LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); tx_money_spent_in_ins += amount; - set_spent(it->second, height); - if (0 != m_callback) - m_callback->on_money_spent(height, tx, amount, tx); + if (!pool) + { + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); + set_spent(it->second, height); + if (0 != m_callback) + m_callback->on_money_spent(height, tx, amount, tx); + } } } @@ -2852,6 +2858,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); req_t.params.unlocked = true; + req_t.params.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; 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(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); @@ -2877,18 +2884,33 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. - uint64_t num_outs = 0; + uint64_t num_outs = 0, num_recent_outs = 0; for (auto he: resp_t.result.histogram) { if (he.amount == amount) { - num_outs = he.instances; + LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " + << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); + num_outs = he.unlocked_instances; + num_recent_outs = he.recent_instances; break; } } LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount)); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); + THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, + "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + + // X% of those outs are to be taken from recent outputs + size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO; + if (recent_outputs_count == 0) + recent_outputs_count = 1; // ensure we have at least one, if possible + if (recent_outputs_count > num_recent_outs) + recent_outputs_count = num_recent_outs; + if (td.m_global_output_index >= num_outs - num_recent_outs) + --recent_outputs_count; // if the real out is recent, pick one less recent fake out + LOG_PRINT_L1("Using " << recent_outputs_count << " recent outputs"); if (num_outs <= requested_outputs_count) { @@ -2918,11 +2940,24 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si // return to the top of the loop and try again, otherwise add it to the // list of output indices we've seen. - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - uint64_t i = (uint64_t)(frac*num_outs); - // just in case rounding up to 1 occurs after sqrt + uint64_t i; + if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with + { + // equiprobable distribution over the recent outs + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_recent_outs) + num_outs - num_recent_outs; + LOG_PRINT_L2("picking " << i << " as recent"); + } + else + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_outs); + LOG_PRINT_L2("picking " << i << " as triangular"); + } + // just in case rounding up to 1 occurs after calc if (i == num_outs) --i; @@ -3662,6 +3697,26 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; + const bool use_rct = use_fork_rules(4, 0); + + // gather all our dust and non dust outputs + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + { + if (td.is_rct() || is_valid_decomposed_amount(td.amount())) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + } + } + + return create_transactions_from(address, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); +} + +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +{ uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { std::list<size_t> selected_transfers; @@ -3673,24 +3728,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono std::vector<TX> txes; uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - const bool use_rct = use_fork_rules(4, 0); + 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_multiplier = get_fee_multiplier(priority, use_new_fee); - // gather all our dust and non dust outputs - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) - { - if (td.is_rct() || is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); - else - unused_dust_indices.push_back(i); - } - } LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); if (unused_dust_indices.empty() && unused_transfers_indices.empty()) @@ -3820,121 +3863,6 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const } return money; } - -template<typename T> -void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t> &extra, cryptonote::transaction& tx, pending_tx &ptx) -{ - using namespace cryptonote; - - uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - - // select all dust inputs for transaction - // throw if there are none - uint64_t money = 0; - std::list<size_t> selected_transfers; -#if 1 - for (size_t n = 0; n < outs.size(); ++n) - { - const transfer_details& td = m_transfers[outs[n]]; - if (!td.m_spent) - { - selected_transfers.push_back (outs[n]); - money += td.amount(); - if (selected_transfers.size() >= num_outputs) - break; - } - } -#else - for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) - { - const transfer_details& td = *i; - if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td)) - { - selected_transfers.push_back (i); - money += td.amount(); - if (selected_transfers.size() >= num_outputs) - break; - } - } -#endif - - // we don't allow no output to self, easier, but one may want to burn the dust if = fee - THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee); - - typedef cryptonote::tx_source_entry::output_entry tx_output_entry; - - //prepare inputs - size_t i = 0; - std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(size_t idx, selected_transfers) - { - sources.resize(sources.size()+1); - cryptonote::tx_source_entry& src = sources.back(); - const transfer_details& td = m_transfers[idx]; - src.amount = td.amount(); - src.rct = td.is_rct(); - - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - return a.first >= td.m_global_output_index; - }); - tx_output_entry real_oe; - real_oe.first = td.m_global_output_index; - real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); - real_oe.second.mask = rct::commit(td.amount(), td.m_mask); - auto interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - detail::print_source_entry(src); - ++i; - } - - cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); - - std::vector<cryptonote::tx_destination_entry> dsts; - uint64_t money_back = money - needed_fee; - if (dust_policy.dust_threshold > 0) - money_back = money_back - money_back % dust_policy.dust_threshold; - dsts.push_back(cryptonote::tx_destination_entry(money_back, m_account_public_address)); - std::vector<cryptonote::tx_destination_entry> splitted_dsts, dust; - destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust); - BOOST_FOREACH(auto& d, dust) { - THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < d.amount, error::wallet_internal_error, "invalid dust value: dust = " + - std::to_string(d.amount) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); - } - - crypto::secret_key tx_key; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); - THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); - - std::string key_images; - bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool - { - CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); - key_images += boost::to_string(in.k_image) + " "; - return true; - }); - THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); - - ptx.key_images = key_images; - ptx.fee = money - money_back; - ptx.dust = 0; - ptx.tx = tx; - ptx.change_dts = change_dts; - ptx.selected_transfers = selected_transfers; - ptx.tx_key = tx_key; - ptx.dests = dsts; - ptx.construction_data.sources = sources; - ptx.construction_data.destinations = dsts; - ptx.construction_data.change_dts = change_dts; - ptx.construction_data.extra = tx.extra; - ptx.construction_data.unlock_time = unlock_time; - ptx.construction_data.use_rct = false; -} - //---------------------------------------------------------------------------------------------------- void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) { @@ -4079,7 +4007,7 @@ uint64_t wallet2::get_num_rct_outputs() THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); - return resp_t.result.histogram[0].instances; + return resp_t.result.histogram[0].total_instances; } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) @@ -4114,100 +4042,17 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo return std::vector<wallet2::pending_tx>(); } - // failsafe split attempt counter - size_t attempt_count = 0; - - for(attempt_count = 1; ;attempt_count++) + // split in "dust" and "non dust" to make it easier to select outputs + std::vector<size_t> unmixable_transfer_outputs, unmixable_dust_outputs; + for (auto n: unmixable_outputs) { - size_t num_tx = 0.5 + pow(1.7,attempt_count-1); - size_t num_outputs_per_tx = (num_dust_outputs + num_tx - 1) / num_tx; - - std::vector<pending_tx> ptx_vector; - try - { - // for each new tx - for (size_t i=0; i<num_tx;++i) - { - cryptonote::transaction tx; - pending_tx ptx; - std::vector<uint8_t> extra; - - // loop until fee is met without increasing tx size to next KB boundary. - uint64_t needed_fee = 0; - do - { - transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); - auto txBlob = t_serializable_object_to_blob(ptx.tx); - needed_fee = calculate_fee(fee_per_kb, txBlob, 1); - - // reroll the tx with the actual amount minus the fee - // if there's not enough for the fee, it'll throw - transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); - txBlob = t_serializable_object_to_blob(ptx.tx); - needed_fee = calculate_fee(fee_per_kb, txBlob, 1); - } while (ptx.fee < needed_fee); - - ptx_vector.push_back(ptx); - - // mark transfers to be used as "spent" - BOOST_FOREACH(size_t idx, ptx.selected_transfers) - { - set_spent(idx, 0); - } - } - - // if we made it this far, we've selected our transactions. committing them will mark them spent, - // so this is a failsafe in case they don't go through - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(size_t idx2, ptx.selected_transfers) - { - set_unspent(idx2); - } - } - - // if we made it this far, we're OK to actually send the transactions - return ptx_vector; - - } - // only catch this here, other exceptions need to pass through to the calling function - catch (const tools::error::tx_too_big& e) - { - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(size_t idx2, ptx.selected_transfers) - { - set_unspent(idx2); - } - } - - if (attempt_count >= MAX_SPLIT_ATTEMPTS) - { - throw; - } - } - catch (...) - { - // in case of some other exception, make sure any tx in queue are marked unspent again - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(size_t idx2, ptx.selected_transfers) - { - set_unspent(idx2); - } - } - - throw; - } + if (m_transfers[n].amount() < fee_per_kb) + unmixable_dust_outputs.push_back(n); + else + unmixable_transfer_outputs.push_back(n); } + + return create_transactions_from(m_account_public_address, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon); } bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 4cd74d4a2..4777102af 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -346,8 +346,6 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); template<typename T> - void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); - template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, @@ -361,6 +359,7 @@ namespace tools std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(bool *same_version = NULL); void get_transfers(wallet2::transfer_container& incoming_transfers) const; diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8d5223006..0f622c26c 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -219,6 +219,20 @@ struct Wallet */ virtual void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0; + /*! + * \brief setRefreshFromBlockHeight - start refresh from block height on recover + * + * \param refresh_from_block_height - blockchain start height + */ + virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; + + /*! + * \brief setRecoveringFromSeed - set state recover form seed + * + * \param recoveringFromSeed - true/false + */ + virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; + /** * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed * @return @@ -255,6 +269,12 @@ struct Wallet */ virtual uint64_t daemonBlockChainTargetHeight() const = 0; + /** + * @brief synchronized - checks if wallet was ever synchronized + * @return + */ + virtual bool synchronized() const = 0; + static std::string displayAmount(uint64_t amount); static uint64_t amountFromString(const std::string &amount); static uint64_t amountFromDouble(double amount); @@ -347,9 +367,11 @@ struct WalletManager * \brief recovers existing wallet using memo (electrum seed) * \param path Name of wallet file to be created * \param memo memo (25 words electrum seed) + * \param testnet testnet + * \param restoreHeight restore from start height * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ - virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet = false) = 0; + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet = false, uint64_t restoreHeight = 0) = 0; /*! * \brief Closes wallet. In case operation succeded, wallet object deleted. in case operation failed, wallet object not deleted diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 45a94b4dd..0468cf40a 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -539,8 +539,10 @@ TEST_F(WalletTest1, WalletReturnsDaemonBlockHeight) TEST_F(WalletTest1, WalletRefresh) { + std::cout << "Opening wallet: " << CURRENT_SRC_WALLET << std::endl; Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); // make sure testnet daemon is running + std::cout << "connecting to daemon: " << TESTNET_DAEMON_ADDRESS << std::endl; ASSERT_TRUE(wallet1->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet1->refresh()); ASSERT_TRUE(wmgr->closeWallet(wallet1)); @@ -570,13 +572,14 @@ TEST_F(WalletTest1, WalletTransaction) ASSERT_TRUE(wallet1->status() == Bitmonero::PendingTransaction::Status_Ok); std::string recepient_address = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); - wallet1->setDefaultMixin(1); - ASSERT_TRUE(wallet1->defaultMixin() == 1); + const int MIXIN_COUNT = 4; + Bitmonero::PendingTransaction * transaction = wallet1->createTransaction(recepient_address, PAYMENT_ID_EMPTY, AMOUNT_10XMR, - 1); + MIXIN_COUNT, + Bitmonero::PendingTransaction::Priority_Medium); ASSERT_TRUE(transaction->status() == Bitmonero::PendingTransaction::Status_Ok); wallet1->refresh(); @@ -1146,10 +1149,10 @@ int main(int argc, char** argv) TESTNET_WALLET5_NAME = WALLETS_ROOT_DIR + "/wallet_05.bin"; TESTNET_WALLET6_NAME = WALLETS_ROOT_DIR + "/wallet_06.bin"; - CURRENT_SRC_WALLET = TESTNET_WALLET6_NAME; - CURRENT_DST_WALLET = TESTNET_WALLET5_NAME; + CURRENT_SRC_WALLET = TESTNET_WALLET5_NAME; + CURRENT_DST_WALLET = TESTNET_WALLET1_NAME; ::testing::InitGoogleTest(&argc, argv); - // Bitmonero::WalletManagerFactory::setLogLevel(Bitmonero::WalletManagerFactory::LogLevel_Max); + Bitmonero::WalletManagerFactory::setLogLevel(Bitmonero::WalletManagerFactory::LogLevel_Max); return RUN_ALL_TESTS(); } diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt index 37accb393..5ec53cd2b 100644 --- a/tests/performance_tests/CMakeLists.txt +++ b/tests/performance_tests/CMakeLists.txt @@ -30,11 +30,12 @@ set(performance_tests_sources main.cpp) set(performance_tests_headers - check_ring_signature.h + check_tx_signature.h cn_slow_hash.h construct_tx.h derive_public_key.h derive_secret_key.h + ge_frombytes_vartime.h generate_key_derivation.h generate_key_image.h generate_key_image_helper.h diff --git a/tests/performance_tests/check_ring_signature.h b/tests/performance_tests/check_tx_signature.h index a55849742..fe595a4da 100644 --- a/tests/performance_tests/check_ring_signature.h +++ b/tests/performance_tests/check_tx_signature.h @@ -36,17 +36,19 @@ #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_format_utils.h" #include "crypto/crypto.h" +#include "ringct/rctSigs.h" #include "multi_tx_test_base.h" -template<size_t a_ring_size> -class test_check_ring_signature : private multi_tx_test_base<a_ring_size> +template<size_t a_ring_size, bool a_rct> +class test_check_tx_signature : private multi_tx_test_base<a_ring_size> { static_assert(0 < a_ring_size, "ring_size must be greater than 0"); public: - static const size_t loop_count = a_ring_size < 100 ? 100 : 10; + static const size_t loop_count = a_rct ? 10 : a_ring_size < 100 ? 100 : 10; static const size_t ring_size = a_ring_size; + static const bool rct = a_rct; typedef multi_tx_test_base<a_ring_size> base_class; @@ -62,7 +64,8 @@ public: std::vector<tx_destination_entry> destinations; destinations.push_back(tx_destination_entry(this->m_source_amount, m_alice.get_keys().m_account_address)); - if (!construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector<uint8_t>(), m_tx, 0)) + crypto::secret_key tx_key; + if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector<uint8_t>(), m_tx, 0, tx_key, rct)) return false; get_transaction_prefix_hash(m_tx, m_tx_prefix_hash); @@ -72,8 +75,18 @@ public: bool test() { - const cryptonote::txin_to_key& txin = boost::get<cryptonote::txin_to_key>(m_tx.vin[0]); - return crypto::check_ring_signature(m_tx_prefix_hash, txin.k_image, this->m_public_key_ptrs, ring_size, m_tx.signatures[0].data()); + if (rct) + { + if (m_tx.rct_signatures.type == rct::RCTTypeFull) + return rct::verRct(m_tx.rct_signatures); + else + return rct::verRctSimple(m_tx.rct_signatures); + } + else + { + const cryptonote::txin_to_key& txin = boost::get<cryptonote::txin_to_key>(m_tx.vin[0]); + return crypto::check_ring_signature(m_tx_prefix_hash, txin.k_image, this->m_public_key_ptrs, ring_size, m_tx.signatures[0].data()); + } } private: diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index d3409c0f3..aef455eaa 100644 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -36,7 +36,7 @@ #include "multi_tx_test_base.h" -template<size_t a_in_count, size_t a_out_count> +template<size_t a_in_count, size_t a_out_count, bool a_rct> class test_construct_tx : private multi_tx_test_base<a_in_count> { static_assert(0 < a_in_count, "in_count must be greater than 0"); @@ -46,6 +46,7 @@ public: static const size_t loop_count = (a_in_count + a_out_count < 100) ? 100 : 10; static const size_t in_count = a_in_count; static const size_t out_count = a_out_count; + static const bool rct = a_rct; typedef multi_tx_test_base<a_in_count> base_class; @@ -68,7 +69,8 @@ public: bool test() { - return cryptonote::construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, m_destinations, std::vector<uint8_t>(), m_tx, 0); + crypto::secret_key tx_key; + return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, m_destinations, std::vector<uint8_t>(), m_tx, 0, tx_key, rct); } private: diff --git a/tests/performance_tests/ge_frombytes_vartime.h b/tests/performance_tests/ge_frombytes_vartime.h new file mode 100644 index 000000000..c815422f9 --- /dev/null +++ b/tests/performance_tests/ge_frombytes_vartime.h @@ -0,0 +1,70 @@ +// 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 + +#pragma once + +#include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_basic.h" + +#include "single_tx_test_base.h" + +class test_ge_frombytes_vartime : public multi_tx_test_base<1> +{ +public: + static const size_t loop_count = 10000; + + typedef multi_tx_test_base<1> base_class; + + bool init() + { + using namespace cryptonote; + + if (!base_class::init()) + return false; + + m_alice.generate(); + + std::vector<tx_destination_entry> destinations; + destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address)); + + return construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector<uint8_t>(), m_tx, 0); + } + + bool test() + { + ge_p3 unp; + const cryptonote::txin_to_key& txin = boost::get<cryptonote::txin_to_key>(m_tx.vin[0]); + return ge_frombytes_vartime(&unp, (const unsigned char*) &txin.k_image) == 0; + } + +private: + cryptonote::account_base m_alice; + cryptonote::transaction m_tx; +}; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index 84a51aa96..d09276230 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -33,10 +33,11 @@ // tests #include "construct_tx.h" -#include "check_ring_signature.h" +#include "check_tx_signature.h" #include "cn_slow_hash.h" #include "derive_public_key.h" #include "derive_secret_key.h" +#include "ge_frombytes_vartime.h" #include "generate_key_derivation.h" #include "generate_key_image.h" #include "generate_key_image_helper.h" @@ -50,31 +51,47 @@ int main(int argc, char** argv) performance_timer timer; timer.start(); - TEST_PERFORMANCE2(test_construct_tx, 1, 1); - TEST_PERFORMANCE2(test_construct_tx, 1, 2); - TEST_PERFORMANCE2(test_construct_tx, 1, 10); - TEST_PERFORMANCE2(test_construct_tx, 1, 100); - TEST_PERFORMANCE2(test_construct_tx, 1, 1000); + TEST_PERFORMANCE3(test_construct_tx, 1, 1, false); + TEST_PERFORMANCE3(test_construct_tx, 1, 2, false); + TEST_PERFORMANCE3(test_construct_tx, 1, 10, false); + TEST_PERFORMANCE3(test_construct_tx, 1, 100, false); + TEST_PERFORMANCE3(test_construct_tx, 1, 1000, false); - TEST_PERFORMANCE2(test_construct_tx, 2, 1); - TEST_PERFORMANCE2(test_construct_tx, 2, 2); - TEST_PERFORMANCE2(test_construct_tx, 2, 10); - TEST_PERFORMANCE2(test_construct_tx, 2, 100); + TEST_PERFORMANCE3(test_construct_tx, 2, 1, false); + TEST_PERFORMANCE3(test_construct_tx, 2, 2, false); + TEST_PERFORMANCE3(test_construct_tx, 2, 10, false); + TEST_PERFORMANCE3(test_construct_tx, 2, 100, false); - TEST_PERFORMANCE2(test_construct_tx, 10, 1); - TEST_PERFORMANCE2(test_construct_tx, 10, 2); - TEST_PERFORMANCE2(test_construct_tx, 10, 10); - TEST_PERFORMANCE2(test_construct_tx, 10, 100); + TEST_PERFORMANCE3(test_construct_tx, 10, 1, false); + TEST_PERFORMANCE3(test_construct_tx, 10, 2, false); + TEST_PERFORMANCE3(test_construct_tx, 10, 10, false); + TEST_PERFORMANCE3(test_construct_tx, 10, 100, false); - TEST_PERFORMANCE2(test_construct_tx, 100, 1); - TEST_PERFORMANCE2(test_construct_tx, 100, 2); - TEST_PERFORMANCE2(test_construct_tx, 100, 10); - TEST_PERFORMANCE2(test_construct_tx, 100, 100); + TEST_PERFORMANCE3(test_construct_tx, 100, 1, false); + TEST_PERFORMANCE3(test_construct_tx, 100, 2, false); + TEST_PERFORMANCE3(test_construct_tx, 100, 10, false); + TEST_PERFORMANCE3(test_construct_tx, 100, 100, false); - TEST_PERFORMANCE1(test_check_ring_signature, 1); - TEST_PERFORMANCE1(test_check_ring_signature, 2); - TEST_PERFORMANCE1(test_check_ring_signature, 10); - TEST_PERFORMANCE1(test_check_ring_signature, 100); + TEST_PERFORMANCE3(test_construct_tx, 2, 1, true); + TEST_PERFORMANCE3(test_construct_tx, 2, 2, true); + TEST_PERFORMANCE3(test_construct_tx, 2, 10, true); + + TEST_PERFORMANCE3(test_construct_tx, 10, 1, true); + TEST_PERFORMANCE3(test_construct_tx, 10, 2, true); + TEST_PERFORMANCE3(test_construct_tx, 10, 10, true); + + TEST_PERFORMANCE3(test_construct_tx, 100, 1, true); + TEST_PERFORMANCE3(test_construct_tx, 100, 2, true); + TEST_PERFORMANCE3(test_construct_tx, 100, 10, true); + + TEST_PERFORMANCE2(test_check_tx_signature, 1, false); + TEST_PERFORMANCE2(test_check_tx_signature, 2, false); + TEST_PERFORMANCE2(test_check_tx_signature, 10, false); + TEST_PERFORMANCE2(test_check_tx_signature, 100, false); + + TEST_PERFORMANCE2(test_check_tx_signature, 2, true); + TEST_PERFORMANCE2(test_check_tx_signature, 10, true); + TEST_PERFORMANCE2(test_check_tx_signature, 100, true); TEST_PERFORMANCE0(test_is_out_to_acc); TEST_PERFORMANCE0(test_generate_key_image_helper); @@ -82,6 +99,7 @@ int main(int argc, char** argv) TEST_PERFORMANCE0(test_generate_key_image); TEST_PERFORMANCE0(test_derive_public_key); TEST_PERFORMANCE0(test_derive_secret_key); + TEST_PERFORMANCE0(test_ge_frombytes_vartime); TEST_PERFORMANCE0(test_cn_slow_hash); diff --git a/tests/performance_tests/multi_tx_test_base.h b/tests/performance_tests/multi_tx_test_base.h index d8898b60d..feabab022 100644 --- a/tests/performance_tests/multi_tx_test_base.h +++ b/tests/performance_tests/multi_tx_test_base.h @@ -59,7 +59,7 @@ public: return false; txout_to_key tx_out = boost::get<txout_to_key>(m_miner_txs[i].vout[0].target); - output_entries.push_back(std::make_pair(i, rct::ctkey({rct::pk2rct(tx_out.key), rct::identity()}))); + output_entries.push_back(std::make_pair(i, rct::ctkey({rct::pk2rct(tx_out.key), rct::zeroCommit(m_miner_txs[i].vout[0].amount)}))); m_public_keys[i] = tx_out.key; m_public_key_ptrs[i] = &m_public_keys[i]; } @@ -72,6 +72,7 @@ public: source_entry.real_output_in_tx_index = 0; source_entry.outputs.swap(output_entries); source_entry.real_output = real_source_idx; + source_entry.mask = rct::identity(); source_entry.rct = false; m_sources.push_back(source_entry); diff --git a/tests/performance_tests/performance_tests.h b/tests/performance_tests/performance_tests.h index 9781349bf..77707148b 100644 --- a/tests/performance_tests/performance_tests.h +++ b/tests/performance_tests/performance_tests.h @@ -142,3 +142,4 @@ void run_test(const char* test_name) #define TEST_PERFORMANCE0(test_class) run_test< test_class >(QUOTEME(test_class)) #define TEST_PERFORMANCE1(test_class, a0) run_test< test_class<a0> >(QUOTEME(test_class<a0>)) #define TEST_PERFORMANCE2(test_class, a0, a1) run_test< test_class<a0, a1> >(QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ">") +#define TEST_PERFORMANCE3(test_class, a0, a1, a2) run_test< test_class<a0, a1, a2> >(QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ", " QUOTEME(a2) ">") diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index b3bd47687..7b4874811 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -106,7 +106,7 @@ public: virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const { return true; } virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const { return true; } virtual bool is_read_only() const { return false; } - virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const { return std::map<uint64_t, uint64_t>(); } + virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); } virtual void add_block( const block& blk , const size_t& block_size diff --git a/utils/gpg_keys/iDunk.asc b/utils/gpg_keys/iDunk.asc new file mode 100644 index 000000000..2c480146b --- /dev/null +++ b/utils/gpg_keys/iDunk.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBFfz7KgBEACtEwDaA/mnVnbhBQAFJT7GuiMWDNGc4uAOcxjOPhXg8aZFXIo/ +MYrxJ/Oo/nBjzlSBW/TDg0AMTwg935bwXA3LYVHdnEaQIRkbwOXfuPPbPLEixQ9E +9SvuzaLOnp58ndwPSfHvNKYHqDX4qOp09jds1Utsqw1WTPq7GDWAyYcqcL+Xd+eZ +QG/Yqo1z4VVoYLR4IspjxsV5CUezFgSqG0dB9lywWjykKI/tIVMc7+1np7xf6STk +mSLDa2UoIDOsEARWttEfLDUNC0k7Kgh3R31b1+kM4Jxh/nzCaetleb2sRHqnc2jl +VgjJwXVRiGIDH3ozdACGvQ4KEqCwfYvgq3WVD1KTnCB6SPrfssOuBNPFmWN+VGx2 +kMAHgSVAagEHdwimNkYgFVONvppezoBhjPkLUJF3Qb8Vt/SrCaYN0ONSc0phlGv0 +oqSDvmlHNp55/eO6G/sV70pcz7XoI+dE3r9IadImLe7bKvND+Sr3oaJFXppBnT3T +8bMK4WCZ4p+0JYB35pAAC05TLXqdBUYG7DoqmTizwfYZm4K3iorA3cLrXTmFOKDC +eNmNiJnxIrx1sWXK4Gj6CscWSmc3ZNpOjE1dhbVoqCKjbdAWmf+XSqP+3K/VSd9s +cjtD4C0i89rnM+iMZQixwIpCgT1fWYmUOiNj9WOVrmRzd6appCtICj/FtQARAQAB +tCppRHVuayA8aUR1bms1NDAwQHVzZXJzLm5vcmVwbHkuZ2l0aHViLmNvbT6JAjgE +EwECACIFAlfz7KgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEA8XFVlt +gS/Yb6MP/jymTTEbCrXz3FL11fFcU6JgwSFB8s6dpfytnBTB4YqOLvhc6G/3PnIK +ukBAJB/uSGMqwO3/OFO2NF0p1o2B2xVCVvSnH+zpR61LCuY02gC6f8lgfd0f0PVF +qFVnSY40W38R6P+W4U9PJm448nB0JYoQqmYIur2PkFhnCc8sAb+r0Wlz6jf/2Sl4 +segrnuAKeRFar4brYo8/P+gyB1cyFzxbGr7ZcVqDGThtgIEnBspnqtPbYTSAQsa7 +WHQkUtRpdyD3yq4mxG/y87VguXnIQsEjwM0aTz1UjEmnktd44LYglASS0y9PUOVF +KEpS5eLcLU2oUPv8KPWsj+3pGAee2qtYe7w9L313hdVGhHMpwGiB0y76fKtuCSCy +SI+48kkhyAS9CRWCFRPnmIW/SXxbjiqYOLVE/usYbb6YZYea4FHu/c8kpE7RQlZr +m5K70sbKdkb3vMU6Ns0vRj0Mv4Y7zxPhwOPddjOvwGrNaVek+t6pYz1j7XAmgz1b +z31UUGfOskjCh8s0xqnV/o740fEp8tSyLu5dJwRz9CKLjhTrfwesZFOIPwy2fJar +v29omukGSlN39YwQGTua1y9iUSU+KgsmK+EPDDRfDwt15J79/UPYMY0+6K+BwskH +IYsxnVEko8wtam1RU4DE65L4i8H8Pz9nlxLbP5JHLRwGrdkWqHM7uQINBFfz7KgB +EACj7PHZfbInkbI2h9qLgVNGeBV6QutvJv58apbV6LZYwRjVh2mtcZIZkAcG+yAF +lT09Y/9BYRl4UI92hK6nF4ggmllZ00+lNyx6GZcGx6U6U1oPDS0LE/qsBT7OiOVd +g3d1Txb6KT6XMbeKrN/EnXOOeYZAvuZVijRv8n4EsbyOKypmWZIz8glS0ZnQBuzA +OudskrykKxxF70afXmXkT6bbr4nzs18MSZTZSdWan1oE1Bx1bKePU/z6O/BTIa1E +7ihy6iyfjyS8wgAx63lKR+hrC8GdDOKd5751a50PLQfR+AkxvkZlZB3vZLyaFTcG +eqrtbBU8tmkUjxskOSFPbJ5j2rXQoo77M+AFQl7twqvgMX1YFnXgzkNADZ/B2fZx +bnfRa9OUeuocX2P9tn3e+1T+6e2VUHnc2GxgtQej6imBwHiAYQ6pbG2iESKBDCzd +o0J9k3bmRQMdWzbGu8gqZIZdmD0jkgrPKo2KQLgzop0O8K2cd544Ikly8zd6iWw0 +Rlq0CoEzKVwxjbcUOUTNxCZHDARU5n4dUIJC8Ibs1Wk+4VGXDQI7JCdGuinJkUBv +7Fs94OlYRRrf5mnMG1UoGkXz5ULdbJyltZU/CM1X1kQGMxM/bM2BKJ+5FNEeyxsp +IOQ4rhMpiOmeoR2qZbSR4nGoO4Twngygw3UZjZWu9ZvbGQARAQABiQIfBBgBAgAJ +BQJX8+yoAhsMAAoJEA8XFVltgS/YrbYQAJ+k4haGMqLhhToswP5VNk2MRfPwIcI5 +UngRK2PrgQED28qsi4YIhQR3ZNwMVdKKZSo49n1LO7FfFGBZ5AKn4Og65QSY4gmZ +HIl0tBDNQouIqrwcFqSCk/W5BOKsAa1WuKReCYCmbSgc77AjT32H/gxFy4PnvecD +1KFYFyn7YTwcrXCIZ8XfL2Gf5zIjj0VlCg/Xasfcpe9G5qGCkgMWWP4WTeh++cLt +3QQZeMMpk4U6i+ouTFoG1OReLZe9hBNGtCbcUEWW6zJnGFN/Htin9T6ybU9/PXP2 +s6HKo61N9SNePhK89ZSFYfyW1MkagaTFf6XY6SB7HJ85e2h0jpLkf0X3HC1jsQ5F +JwoNf4pUJBYJoKau3elk7o5nrQv7ysh1fw3YPmnQIETlmGbOVKmpog6Y9tF3/be3 +LyU3Pk+L+t3jeQ3MXoJnlS3BV5/w8CP9n/iGd7+EfMWtkNeVOjEZpfPwV0kzzO5c +QIv+pWmev7b1S7zCofrOsRm67rtMDu/p7zat+0VUa5i7C80uRVdvjDFl6F+9ZYNW +nzu5NPYHR6kS2zTDGoOXqkmgZI/8LcBW3Vfl/rUoD5BJ+EC2ecRgPn5vB8cv8V9L +3qjHpuHBM18y99oxU8khmzbLKqJKaRR8Ck8BsWT+Lp5lVlZoS3842Hk1lLlsZWRj +XFy6CKMGiqc7 +=IXj5 +-----END PGP PUBLIC KEY BLOCK----- |