diff options
-rw-r--r-- | src/blockchain_db/blockchain_db.h | 3 | ||||
-rw-r--r-- | src/blockchain_db/lmdb/db_lmdb.cpp | 36 | ||||
-rw-r--r-- | src/blockchain_db/lmdb/db_lmdb.h | 3 | ||||
-rw-r--r-- | src/cryptonote_config.h | 2 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 4 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 3 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 2 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.h | 2 | ||||
-rw-r--r-- | src/daemon/rpc_command_executor.cpp | 6 | ||||
-rw-r--r-- | src/ringct/rctOps.cpp | 31 | ||||
-rw-r--r-- | src/ringct/rctOps.h | 4 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 142 | ||||
-rw-r--r-- | src/ringct/rctSigs.h | 2 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 19 | ||||
-rw-r--r-- | src/rpc/core_rpc_server_commands_defs.h | 15 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 186 | ||||
-rw-r--r-- | src/wallet/api/transaction_history.cpp | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 9 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 78 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 2 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 27 | ||||
-rw-r--r-- | tests/unit_tests/hardfork.cpp | 2 |
22 files changed, 418 insertions, 162 deletions
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/cryptonote_config.h b/src/cryptonote_config.h index 319e18808..66084da3c 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -127,7 +127,7 @@ namespace config { uint64_t const DEFAULT_FEE_ATOMIC_XMR_PER_KB = 500; // Just a placeholder! Change me! uint8_t const FEE_CALCULATION_MAX_RETRIES = 10; - uint64_t const DEFAULT_DUST_THRESHOLD = ((uint64_t)10000000000); // pow(10, 10) + uint64_t const DEFAULT_DUST_THRESHOLD = ((uint64_t)2000000000); // 2 * pow(10, 9) uint64_t const BASE_REWARD_CLAMP_THRESHOLD = ((uint64_t)100000000); // pow(10, 8) std::string const P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY = "0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9d2bf9332..9ea023a4c 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3757,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 9a44d9d3f..a82d96416 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -616,7 +616,7 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const uint64_t count) + 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; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 407e89197..d925a184d 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -605,7 +605,7 @@ namespace cryptonote * * @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 uint64_t count); + std::pair<uint64_t, uint64_t> get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); private: diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index db0d0a6e4..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,10 +1263,10 @@ 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; diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index 488e47ca0..239168388 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -320,7 +320,7 @@ namespace rct { //be careful these are also in crypto namespace //cn_fast_hash for arbitrary multiples of 32 bytes void cn_fast_hash(key &hash, const void * data, const std::size_t l) { - keccak((uint8_t *)data, l, hash.bytes, 32); + keccak((const uint8_t *)data, l, hash.bytes, 32); } void hash_to_scalar(key &hash, const void * data, const std::size_t l) { @@ -330,7 +330,7 @@ namespace rct { //cn_fast_hash for a 32 byte key void cn_fast_hash(key & hash, const key & in) { - keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32); } void hash_to_scalar(key & hash, const key & in) { @@ -341,7 +341,7 @@ namespace rct { //cn_fast_hash for a 32 byte key key cn_fast_hash(const key & in) { key hash; - keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32); return hash; } @@ -354,7 +354,7 @@ namespace rct { //cn_fast_hash for a 128 byte unsigned char key cn_fast_hash128(const void * in) { key hash; - keccak((uint8_t *)in, 128, hash.bytes, 32); + keccak((const uint8_t *)in, 128, hash.bytes, 32); return hash; } @@ -367,20 +367,13 @@ namespace rct { //cn_fast_hash for multisig purpose //This takes the outputs and commitments //and hashes them into a 32 byte sized key - key cn_fast_hash(ctkeyV PC) { - key rv = identity(); - std::size_t l = (std::size_t)PC.size(); - size_t i = 0, j = 0; - vector<char> m(l * 64); - for (i = 0 ; i < l ; i++) { - memcpy(&m[i * 64], &PC[i].dest, 32); - memcpy(&m[i * 64 + 32], &PC[i].mask, 32); - } - cn_fast_hash(rv, &m[0], 64*l); + key cn_fast_hash(const ctkeyV &PC) { + key rv; + cn_fast_hash(rv, &PC[0], 64*PC.size()); return rv; } - key hash_to_scalar(ctkeyV PC) { + key hash_to_scalar(const ctkeyV &PC) { key rv = cn_fast_hash(PC); sc_reduce32(rv.bytes); return rv; @@ -391,14 +384,8 @@ namespace rct { //put them in the key vector and it concatenates them //and then hashes them key cn_fast_hash(const keyV &keys) { - size_t l = keys.size(); - vector<unsigned char> m(l * 32); - size_t i; - for (i = 0 ; i < l ; i++) { - memcpy(&m[i * 32], keys[i].bytes, 32); - } key rv; - cn_fast_hash(rv, &m[0], 32 * l); + cn_fast_hash(rv, &keys[0], keys.size() * sizeof(keys[0])); //dp(rv); return rv; } diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 1e71c645d..a7e13eefa 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -149,8 +149,8 @@ namespace rct { //for mg sigs key cn_fast_hash128(const void * in); key hash_to_scalar128(const void * in); - key cn_fast_hash(ctkeyV PC); - key hash_to_scalar(ctkeyV PC); + key cn_fast_hash(const ctkeyV &PC); + key hash_to_scalar(const ctkeyV &PC); //for mg sigs key cn_fast_hash(const keyV &keys); key hash_to_scalar(const keyV &keys); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 80ece9c44..f7ea3729d 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -28,14 +28,26 @@ // 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/util.h" #include "rctSigs.h" #include "cryptonote_core/cryptonote_format_utils.h" 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 { //Schnorr Non-linkable @@ -43,7 +55,7 @@ namespace rct { //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 //These are called in the below ASNL sig generation - void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index) { + void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, unsigned int index) { key c1, c2, L2; key a = skGen(); if (index == 0) { @@ -95,7 +107,7 @@ namespace rct { asnlSig rv; rv.s = zero(); for (j = 0; j < ATOMS; j++) { - GenSchnorrNonLinkable(rv.L1[j], s1[j], rv.s2[j], x[j], P1[j], P2[j], (int)indices[j]); + GenSchnorrNonLinkable(rv.L1[j], s1[j], rv.s2[j], x[j], P1[j], P2[j], indices[j]); sc_add(rv.s.bytes, rv.s.bytes, s1[j].bytes); } return rv; @@ -348,9 +360,14 @@ 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; + hashes.reserve(3); hashes.push_back(rv.message); crypto::hash h; @@ -364,6 +381,7 @@ namespace rct { hashes.push_back(hash2rct(h)); keyV kv; + kv.reserve((64*3+1) * rv.p.rangeSigs.size()); for (auto r: rv.p.rangeSigs) { for (size_t n = 0; n < 64; ++n) @@ -526,6 +544,10 @@ 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 //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with @@ -743,17 +765,41 @@ namespace rct { // some rct ops can throw try { - size_t i = 0; - bool tmp; + 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 = threads > 1; + 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(); }); + DP("range proofs verified?"); - for (i = 0; i < rv.outPk.size(); i++) { - tmp = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - DP(tmp); - if (!tmp) { - LOG_ERROR("Range proof verification failed for input " << i); + 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; + } + } + } + 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; } + } } + //compute txn fee key txnFeeKey = scalarmultH(d2h(rv.txnFee)); bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv)); @@ -784,29 +830,87 @@ 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"); - key sumOutpks = identity(); - for (i = 0; i < rv.outPk.size(); i++) { - if (!verRange(rv.outPk[i].mask, rv.p.rangeSigs[i])) { + { + 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 = threads > 1; + 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; } + } + 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; + } + } + } + } + + key sumOutpks = identity(); + for (i = 0; i < rv.outPk.size(); i++) { addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask); } DP(sumOutpks); key txnFeeKey = scalarmultH(d2h(rv.txnFee)); addKeys(sumOutpks, txnFeeKey, sumOutpks); - bool tmpb = false; key message = get_pre_mlsag_hash(rv); - key sumPseudoOuts = identity(); - for (i = 0 ; i < rv.mixRing.size() ; i++) { - tmpb = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); - addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); - DP(tmpb); - if (!tmpb) { + + { + 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 = threads > 1; + 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]))); + } + 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; + } } + } + } + + key sumPseudoOuts = identity(); + for (i = 0 ; i < rv.mixRing.size() ; i++) { + addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); } DP(sumPseudoOuts); diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 11d771818..a4fecade4 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -70,7 +70,7 @@ namespace rct { //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 //These are called in the below ASNL sig generation - void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index); + void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, unsigned int index); bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2); //Aggregate Schnorr Non-linkable Ring Signature (ASNL) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e8b2d5cb1..0fca2eb57 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -142,6 +142,7 @@ namespace cryptonote res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; + res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); res.status = CORE_RPC_STATUS_OK; return true; } @@ -1092,7 +1093,14 @@ namespace cryptonote return false; } - res.height = m_core.get_current_blockchain_height(); + crypto::hash top_hash; + if (!m_core.get_blockchain_top(res.height, top_hash)) + { + res.status = "Failed"; + return false; + } + ++res.height; // turn top block height into blockchain height + res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; @@ -1105,6 +1113,7 @@ namespace cryptonote res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; + res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); res.status = CORE_RPC_STATUS_OK; return true; } @@ -1236,10 +1245,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 +1260,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; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 61c302e45..64f7ebf5e 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -464,6 +464,7 @@ namespace cryptonote uint64_t grey_peerlist_size; bool testnet; std::string top_block_hash; + uint64_t cumulative_difficulty; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -480,6 +481,7 @@ namespace cryptonote KV_SERIALIZE(grey_peerlist_size) KV_SERIALIZE(testnet) KV_SERIALIZE(top_block_hash) + KV_SERIALIZE(cumulative_difficulty) END_KV_SERIALIZE_MAP() }; }; @@ -1172,26 +1174,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() {} }; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b27113473..baa70bc0b 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -873,65 +873,79 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::ask_wallet_create_if_needed() { + LOG_PRINT_L3("simple_wallet::ask_wallet_create_if_needed() started"); std::string wallet_path; - - bool valid_path = false; - do { - wallet_path = command_line::input_line( - tr("Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" - "Wallet file name: ") - ); - if (std::cin.eof()) - { - return false; - } - valid_path = tools::wallet2::wallet_valid_path_format(wallet_path); - if (!valid_path) - { - fail_msg_writer() << tr("wallet file path not valid: ") << wallet_path; - } - } - while (!valid_path); - + std::string confirm_creation; + bool wallet_name_valid = false; bool keys_file_exists; bool wallet_file_exists; - tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists); - LOG_PRINT_L3("wallet_path: " << wallet_path << ""); - LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha - << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); - LOG_PRINT_L1("Loading wallet..."); - - // add logic to error out if new wallet requested but named wallet file exists - if (keys_file_exists || wallet_file_exists) - { - if (!m_generate_new.empty() || m_restoring) - { - fail_msg_writer() << tr("attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); - return false; - } - } + do{ + LOG_PRINT_L3("User asked to specify wallet file name."); + wallet_path = command_line::input_line( + tr("Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" + "Wallet file name (or Ctrl-C to quit): ") + ); + if(std::cin.eof()) + { + LOG_ERROR("Unexpected std::cin.eof() - Exited simple_wallet::ask_wallet_create_if_needed()"); + return false; + } + if(!tools::wallet2::wallet_valid_path_format(wallet_path)) + { + fail_msg_writer() << tr("Wallet name not valid. Please try again or use Ctrl-C to quit."); + wallet_name_valid = false; + } + else + { + tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists); + LOG_PRINT_L3("wallet_path: " << wallet_path << ""); + LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha + << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); - bool r; - if(keys_file_exists) - { - m_wallet_file=wallet_path; - r = true; - }else - { - if(!wallet_file_exists) - { - std::cout << tr("The wallet doesn't exist, generating new one") << std::endl; - m_generate_new = wallet_path; - r = true; - }else - { - fail_msg_writer() << tr("keys file not found: failed to open wallet: ") << "\"" << wallet_path << "\"."; - r = false; - } - } + if((keys_file_exists || wallet_file_exists) && (!m_generate_new.empty() || m_restoring)) + { + fail_msg_writer() << tr("Attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); + return false; + } + if(wallet_file_exists && keys_file_exists) //Yes wallet, yes keys + { + success_msg_writer() << tr("Wallet and key files found, loading..."); + m_wallet_file = wallet_path; + return true; + } + else if(!wallet_file_exists && keys_file_exists) //No wallet, yes keys + { + success_msg_writer() << tr("Key file found but not wallet file. Regenerating..."); + m_wallet_file = wallet_path; + return true; + } + else if(wallet_file_exists && !keys_file_exists) //Yes wallet, no keys + { + fail_msg_writer() << tr("Key file not found. Failed to open wallet: ") << "\"" << wallet_path << "\". Exiting."; + return false; + } + else if(!wallet_file_exists && !keys_file_exists) //No wallet, no keys + { + message_writer() << tr("No wallet/key file found with that name. Confirm creation of new wallet named: ") << wallet_path; + confirm_creation = command_line::input_line(tr("(y)es/(n)o: ")); + if(std::cin.eof()) + { + LOG_ERROR("Unexpected std::cin.eof() - Exited simple_wallet::ask_wallet_create_if_needed()"); + return false; + } + if(is_it_true(confirm_creation)) + { + success_msg_writer() << tr("Generating new wallet..."); + m_generate_new = wallet_path; + return true; + } + } + } + } while(!wallet_name_valid); - return r; + LOG_ERROR("Failed out of do-while loop in ask_wallet_create_if_needed()"); + return false; } /*! @@ -2550,12 +2564,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("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())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -2811,12 +2832,19 @@ bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("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())); - fail_msg_writer() << tr("Not enough money to transfer."); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -2897,11 +2925,8 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; - for (const auto &vin: ptx_vector[n].tx.vin) - { - if (vin.type() == typeid(txin_to_key)) - total_unmixable += boost::get<txin_to_key>(vin).amount; - } + for (auto i: ptx_vector[n].selected_transfers) + total_unmixable += m_wallet->get_transfer_details(i).amount(); } std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable); @@ -2970,12 +2995,19 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("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())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -3152,11 +3184,8 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; - for (const auto &vin: ptx_vector[n].tx.vin) - { - if (vin.type() == typeid(txin_to_key)) - total_sent += boost::get<txin_to_key>(vin).amount; - } + for (auto i: ptx_vector[n].selected_transfers) + total_sent += m_wallet->get_transfer_details(i).amount(); } std::string prompt_str; @@ -3225,12 +3254,19 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("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())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -3458,11 +3494,19 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) } catch (const tools::error::not_enough_money& e) { - fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("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()); + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -3831,8 +3875,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) m_wallet->get_payments_out(payments, min_height, max_height); 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 fee = pd.m_amount_in - pd.m_amount_out; 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; std::string dests; for (const auto &d: pd.m_dests) { if (!dests.empty()) diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index e4a003b02..2ba5f3620 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -156,8 +156,8 @@ void TransactionHistoryImpl::refresh() const crypto::hash &hash = i->first; const tools::wallet2::confirmed_transfer_details &pd = i->second; - uint64_t fee = pd.m_amount_in - pd.m_amount_out; 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; 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 d1c849537..94d62666a 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -595,6 +595,15 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const 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()) % diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d09e99518..b499f6b24 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(); \ @@ -2283,9 +2286,9 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (size_t i = 0; i < selected_transfers.size(); ++i) + for (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { - float r = get_output_relatedness(candidate, transfers[i]); + float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) { relatedness = r; @@ -2854,6 +2857,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"); @@ -2868,8 +2872,10 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + size_t num_selected_transfers = 0; for(size_t idx: selected_transfers) { + ++num_selected_transfers; const transfer_details &td = m_transfers[idx]; const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set<uint64_t> seen_indices; @@ -2879,18 +2885,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) { @@ -2920,11 +2941,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; @@ -2958,7 +2992,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; - outs.reserve(selected_transfers.size()); + outs.reserve(num_selected_transfers); for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; @@ -3456,6 +3490,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); + // early out if we know we can't make it anyway + // we could also check for being within FEE_PER_KB, but if the fee calculation + // ever changes, this might be missed, so let this go through + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(), error::not_enough_money, + unlocked_balance(), needed_money, 0); + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) return std::vector<wallet2::pending_tx>(); @@ -3496,7 +3536,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // if we need to spend money and don't have any left, we fail if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { LOG_PRINT_L2("No more outputs to choose from"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) @@ -3636,7 +3676,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (adding_fee) { LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << @@ -3696,7 +3736,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton 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 = 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_multiplier = get_fee_multiplier(priority, use_new_fee); @@ -3974,20 +4014,26 @@ 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; +} +//---------------------------------------------------------------------------------------------------- +const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const +{ + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Bad transfer index"); + return m_transfers[idx]; } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances - const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 return select_available_outputs_from_histogram(min_mixin + 1, false, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with - const size_t min_mixin = use_fork_rules(5, 10) ? 2 : 4; // v5 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 return select_available_outputs_from_histogram(min_mixin + 1, true, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 20e157b9c..fa9797219 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -422,7 +422,6 @@ namespace tools * \return Whether path is valid format */ static bool wallet_valid_path_format(const std::string& file_path); - static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); @@ -447,6 +446,7 @@ namespace tools bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; uint64_t get_num_rct_outputs(); + const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); bool use_fork_rules(uint8_t version, int64_t early_blocks = 0); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index c5590d79c..93e7c2ec3 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -68,6 +68,7 @@ namespace tools // transfer_error * // get_random_outs_general_error // not_enough_money + // tx_not_possible // not_enough_outs_to_mix // tx_not_constructed // tx_rejected @@ -351,6 +352,32 @@ namespace tools : transfer_error(std::move(loc), "not enough money") , m_available(availbable) , m_tx_amount(tx_amount) + { + } + + uint64_t available() const { return m_available; } + uint64_t tx_amount() const { return m_tx_amount; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << + ", available = " << cryptonote::print_money(m_available) << + ", tx_amount = " << cryptonote::print_money(m_tx_amount); + return ss.str(); + } + + private: + uint64_t m_available; + uint64_t m_tx_amount; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_not_possible : public transfer_error + { + explicit tx_not_possible(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + : transfer_error(std::move(loc), "tx not possible") + , m_available(availbable) + , m_tx_amount(tx_amount) , m_fee(fee) { } 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 |