From 5f146873c58f39632e26c5edbf2f618cacbd76a5 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 18 Feb 2018 10:47:25 +0000 Subject: wallet: add shared ring database This maps key images to rings, so that different forks can reuse the rings by key image. This avoids revealing the real inputs like would happen if two forks spent the same outputs with different rings. This database is meant to be shared with all Monero forks which don't bother making a new chain, putting users' privacy at risk in the process. It is placed in a shared data directory by default ($HOME/.shared-ringdb on UNIX like systems). You may use --shared-ringdb-dir to override this location, and should then do so for all Monero forks for them to share the database. --- src/wallet/ringdb.cpp | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 src/wallet/ringdb.cpp (limited to 'src/wallet/ringdb.cpp') diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp new file mode 100644 index 000000000..5ea8c5fec --- /dev/null +++ b/src/wallet/ringdb.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2018, 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 +#include +#include +#include +#include "misc_log_ex.h" +#include "misc_language.h" +#include "wallet_errors.h" +#include "ringdb.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.ringdb" + +static int compare_hash32(const MDB_val *a, const MDB_val *b) +{ + uint32_t *va = (uint32_t*) a->mv_data; + uint32_t *vb = (uint32_t*) b->mv_data; + for (int n = 7; n >= 0; n--) + { + if (va[n] == vb[n]) + continue; + return va[n] < vb[n] ? -1 : 1; + } + + return 0; +} + +static std::string compress_ring(const std::vector &ring) +{ + std::string s; + for (uint64_t out: ring) + s += tools::get_varint_data(out); + return s; +} + +static std::vector decompress_ring(const std::string &s) +{ + std::vector ring; + int read = 0; + for (std::string::const_iterator i = s.begin(); i != s.cend(); std::advance(i, read)) + { + uint64_t out; + std::string tmp(i, s.cend()); + read = tools::read_varint(tmp.begin(), tmp.end(), out); + THROW_WALLET_EXCEPTION_IF(read <= 0 || read > 256, tools::error::wallet_internal_error, "Internal error decompressing ring"); + ring.push_back(out); + } + return ring; +} + +std::string get_rings_filename(boost::filesystem::path filename) +{ + if (!boost::filesystem::is_directory(filename)) + filename.remove_filename(); + return filename.string(); +} + +static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + static const char salt[] = "ringdsb"; + + uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(salt)]; + memcpy(buffer, &key_image, sizeof(key_image)); + memcpy(buffer + sizeof(key_image), &key, sizeof(key)); + memcpy(buffer + sizeof(key_image) + sizeof(key), salt, sizeof(salt)); + crypto::hash hash; + crypto::cn_fast_hash(buffer, sizeof(buffer), hash.data); + static_assert(sizeof(hash) >= CHACHA_IV_SIZE, "Incompatible hash and chacha IV sizes"); + crypto::chacha_iv iv; + memcpy(&iv, &hash, CHACHA_IV_SIZE); + return iv; +} + +static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + const crypto::chacha_iv iv = make_iv(key_image, key); + std::string ciphertext; + ciphertext.resize(plaintext.size() + sizeof(iv)); + crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + memcpy(&ciphertext[0], &iv, sizeof(iv)); + return ciphertext; +} + +static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key); +} + +static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + const crypto::chacha_iv iv = make_iv(key_image, key); + std::string plaintext; + THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(iv), tools::error::wallet_internal_error, "Bad ciphertext text"); + plaintext.resize(ciphertext.size() - sizeof(iv)); + crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - sizeof(iv), key, iv, &plaintext[0]); + return plaintext; +} + +static int resize_env(MDB_env *env, const char *db_path, size_t n_entries) +{ + MDB_envinfo mei; + MDB_stat mst; + int ret; + + ret = mdb_env_info(env, &mei); + if (ret) + return ret; + ret = mdb_env_stat(env, &mst); + if (ret) + return ret; + uint64_t size_used = mst.ms_psize * mei.me_last_pgno; + const size_t needed = n_entries * (32 + 1024); // highball 1kB for the ring data to make sure + uint64_t mapsize = mei.me_mapsize; + if (size_used + needed > mei.me_mapsize) + { + try + { + boost::filesystem::path path(db_path); + boost::filesystem::space_info si = boost::filesystem::space(path); + if(si.available < needed) + { + MERROR("!! WARNING: Insufficient free space to extend database !!: " << (si.available >> 20L) << " MB available"); + return ENOSPC; + } + } + catch(...) + { + // print something but proceed. + MWARNING("Unable to query free disk space."); + } + + mapsize += needed; + } + return mdb_env_set_mapsize(env, mapsize); +} + +namespace tools { namespace ringdb +{ + +bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + MDB_env *env; + MDB_dbi dbi; + MDB_txn *txn; + int dbr; + bool tx_active = false; + + if (filename.empty()) + return true; + tools::create_directories_if_necessary(filename); + + dbr = mdb_env_create(&env); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_set_maxdbs(env, 1); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); + dbr = resize_env(env, filename.c_str(), tx.vin.size()); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); + mdb_set_compare(txn, dbi, compare_hash32); + + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + + MDB_val key, data; + std::string key_ciphertext = encrypt(txin.k_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + MDEBUG("Saving relative ring for key image " << txin.k_image << ": " << + boost::join(txin.key_offsets | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + std::string compressed_ring = compress_ring(txin.key_offsets); + std::string data_ciphertext = encrypt(compressed_ring, txin.k_image, chacha_key); + data.mv_size = data_ciphertext.size(); + data.mv_data = (void*)data_ciphertext.c_str(); + dbr = mdb_put(txn, dbi, &key, &data, 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to add ring to database: " + std::string(mdb_strerror(dbr))); + } + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn adding ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + MDB_env *env; + MDB_dbi dbi; + MDB_txn *txn; + int dbr; + bool tx_active = false; + + if (filename.empty()) + return true; + tools::create_directories_if_necessary(filename); + + dbr = mdb_env_create(&env); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_set_maxdbs(env, 1); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); + dbr = resize_env(env, filename.c_str(), tx.vin.size()); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); + mdb_set_compare(txn, dbi, compare_hash32); + + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + + MDB_val key, data; + std::string key_ciphertext = encrypt(txin.k_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + + dbr = mdb_get(txn, dbi, &key, &data); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + if (dbr == MDB_NOTFOUND) + continue; + THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + + MDEBUG("Removing ring data for key image " << txin.k_image); + dbr = mdb_del(txn, dbi, &key, NULL); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to remove ring to database: " + std::string(mdb_strerror(dbr))); + } + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn removing ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs) +{ + MDB_env *env; + MDB_dbi dbi; + MDB_txn *txn; + int dbr; + bool tx_active = false; + + if (filename.empty()) + return false; + tools::create_directories_if_necessary(filename); + + dbr = mdb_env_create(&env); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_set_maxdbs(env, 1); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); + dbr = resize_env(env, filename.c_str(), 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); + mdb_set_compare(txn, dbi, compare_hash32); + + MDB_val key, data; + std::string key_ciphertext = encrypt(key_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + dbr = mdb_get(txn, dbi, &key, &data); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + if (dbr == MDB_NOTFOUND) + return false; + THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + + std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key); + outs = decompress_ring(data_plaintext); + MDEBUG("Found ring for key image " << key_image << ":"); + MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + outs = cryptonote::relative_output_offsets_to_absolute(outs); + MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +}} -- cgit v1.2.3 From d29ea0455a34c2d08c681dacdf809e7f7317d2b3 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 25 Feb 2018 19:20:07 +0000 Subject: wallet: add an output blackball list to avoid using those in rings --- src/simplewallet/simplewallet.cpp | 141 ++++++++++++++++++++++++++++++++++++++ src/simplewallet/simplewallet.h | 3 + src/wallet/ringdb.cpp | 120 ++++++++++++++++++++++++++++++-- src/wallet/ringdb.h | 5 ++ src/wallet/wallet2.cpp | 31 ++++++++- src/wallet/wallet2.h | 5 ++ 6 files changed, 297 insertions(+), 8 deletions(-) (limited to 'src/wallet/ringdb.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 698ecced8..315d91aa6 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1332,6 +1332,135 @@ bool simple_wallet::print_ring(const std::vector &args) return true; } +bool simple_wallet::blackball(const std::vector &args) +{ + crypto::public_key output; + if (args.size() == 0) + { + fail_msg_writer() << tr("usage: blackball | [add]"); + return true; + } + + try + { + if (epee::string_tools::hex_to_pod(args[0], output)) + { + m_wallet->blackball_output(output); + } + else if (epee::file_io_utils::is_file_exist(args[0])) + { + std::vector outputs; + char str[65]; + + std::unique_ptr f(fopen(args[0].c_str(), "r")); + if (f) + { + while (!feof(f.get())) + { + if (!fgets(str, sizeof(str), f.get())) + break; + const size_t len = strlen(str); + if (len > 0 && str[len - 1] == '\n') + str[len - 1] = 0; + if (!str[0]) + continue; + outputs.push_back(crypto::public_key()); + if (!epee::string_tools::hex_to_pod(str, outputs.back())) + { + fail_msg_writer() << tr("Invalid public key: ") << str; + return true; + } + } + f.reset(); + bool add = false; + if (args.size() > 1) + { + if (args[1] != "add") + { + fail_msg_writer() << tr("Bad argument: ") + args[1] + ": " + tr("should be \"add\""); + return true; + } + add = true; + } + m_wallet->set_blackballed_outputs(outputs, add); + } + else + { + fail_msg_writer() << tr("Failed to open file"); + return true; + } + } + else + { + fail_msg_writer() << tr("Invalid public key, and file doesn't exist"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to blackball output: ") << e.what(); + } + + return true; +} + +bool simple_wallet::unblackball(const std::vector &args) +{ + crypto::public_key output; + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: unblackball "); + return true; + } + + if (!epee::string_tools::hex_to_pod(args[0], output)) + { + fail_msg_writer() << tr("Invalid public key"); + return true; + } + + try + { + m_wallet->unblackball_output(output); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to unblackball output: ") << e.what(); + } + + return true; +} + +bool simple_wallet::blackballed(const std::vector &args) +{ + crypto::public_key output; + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: blackballed "); + return true; + } + + if (!epee::string_tools::hex_to_pod(args[0], output)) + { + fail_msg_writer() << tr("Invalid public key"); + return true; + } + + try + { + if (m_wallet->is_output_blackballed(output)) + message_writer() << tr("Blackballed: ") << output; + else + message_writer() << tr("not blackballed: ") << output; + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to unblackball output: ") << e.what(); + } + + return true; +} + bool simple_wallet::save_known_rings(const std::vector &args) { try @@ -2041,6 +2170,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::save_known_rings, this, _1), tr("save_known_rings"), tr("Save known rings to the shared rings database")); + m_cmd_binder.set_handler("blackball", + boost::bind(&simple_wallet::blackball, this, _1), + tr("blackball | [add]"), + tr("Blackball output(s) so they never get selected as fake outputs in a ring")); + m_cmd_binder.set_handler("unblackball", + boost::bind(&simple_wallet::unblackball, this, _1), + tr("unblackball "), + tr("Unblackballs an output so it may get selected as a fake output in a ring")); + m_cmd_binder.set_handler("blackballed", + boost::bind(&simple_wallet::blackballed, this, _1), + tr("blackballed "), + tr("Checks whether an output is blackballed")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 9846c82f3..372057022 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -212,6 +212,9 @@ namespace cryptonote bool export_raw_multisig(const std::vector& args); bool print_ring(const std::vector& args); bool save_known_rings(const std::vector& args); + bool blackball(const std::vector& args); + bool unblackball(const std::vector& args); + bool blackballed(const std::vector& args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 5ea8c5fec..198312dc4 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -38,6 +38,9 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.ringdb" +static const char zerokey[8] = {0}; +static const MDB_val zerokeyval = { sizeof(zerokey), (void *)zerokey }; + static int compare_hash32(const MDB_val *a, const MDB_val *b) { uint32_t *va = (uint32_t*) a->mv_data; @@ -123,12 +126,14 @@ static std::string decrypt(const std::string &ciphertext, const crypto::key_imag return plaintext; } -static int resize_env(MDB_env *env, const char *db_path, size_t n_entries) +static int resize_env(MDB_env *env, const char *db_path, size_t needed) { MDB_envinfo mei; MDB_stat mst; int ret; + needed = std::max(needed, (size_t)(2ul * 1024 * 1024)); // at least 2 MB + ret = mdb_env_info(env, &mei); if (ret) return ret; @@ -136,7 +141,6 @@ static int resize_env(MDB_env *env, const char *db_path, size_t n_entries) if (ret) return ret; uint64_t size_used = mst.ms_psize * mei.me_last_pgno; - const size_t needed = n_entries * (32 + 1024); // highball 1kB for the ring data to make sure uint64_t mapsize = mei.me_mapsize; if (size_used + needed > mei.me_mapsize) { @@ -161,6 +165,90 @@ static int resize_env(MDB_env *env, const char *db_path, size_t n_entries) return mdb_env_set_mapsize(env, mapsize); } +static size_t get_ring_data_size(size_t n_entries) +{ + return n_entries * (32 + 1024); // highball 1kB for the ring data to make sure +} + +enum { BLACKBALL_BLACKBALL, BLACKBALL_UNBLACKBALL, BLACKBALL_QUERY, BLACKBALL_CLEAR}; + +static bool blackball_worker(const std::string &filename, const crypto::public_key &output, int op) +{ + MDB_env *env; + MDB_dbi dbi; + MDB_txn *txn; + MDB_cursor *cursor; + int dbr; + bool tx_active = false; + bool ret = true; + + if (filename.empty()) + return true; + tools::create_directories_if_necessary(filename); + + dbr = mdb_env_create(&env); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_set_maxdbs(env, 1); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); + dbr = resize_env(env, filename.c_str(), 32 * 2); // a pubkey, and some slack + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + dbr = mdb_dbi_open(txn, "blackballs", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); + mdb_set_dupsort(txn, dbi, compare_hash32); + + MDB_val key = zerokeyval; + MDB_val data; + data.mv_data = (void*)&output; + data.mv_size = sizeof(output); + + switch (op) + { + case BLACKBALL_BLACKBALL: + MDEBUG("Blackballing output " << output); + dbr = mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA); + if (dbr == MDB_KEYEXIST) + dbr = 0; + break; + case BLACKBALL_UNBLACKBALL: + MDEBUG("Unblackballing output " << output); + dbr = mdb_del(txn, dbi, &key, &data); + if (dbr == MDB_NOTFOUND) + dbr = 0; + break; + case BLACKBALL_QUERY: + MDEBUG("Querying blackball status for output " << output); + dbr = mdb_cursor_open(txn, dbi, &cursor); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); + dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); + MDEBUG("Querying blackball status for output " << output << ": " << std::string(mdb_strerror(dbr))); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); + ret = dbr != MDB_NOTFOUND; + if (dbr == MDB_NOTFOUND) + dbr = 0; + mdb_cursor_close(cursor); + break; + case BLACKBALL_CLEAR: + dbr = mdb_drop(txn, dbi, 0); + break; + default: + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); + } + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn blackballing output to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return ret; +} + namespace tools { namespace ringdb { @@ -183,8 +271,8 @@ bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); - dbr = resize_env(env, filename.c_str(), tx.vin.size()); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = resize_env(env, filename.c_str(), get_ring_data_size(tx.vin.size())); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); @@ -242,8 +330,8 @@ bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_ dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); - dbr = resize_env(env, filename.c_str(), tx.vin.size()); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = resize_env(env, filename.c_str(), 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); @@ -337,4 +425,24 @@ bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, return true; } +bool blackball(const std::string &filename, const crypto::public_key &output) +{ + return blackball_worker(filename, output, BLACKBALL_BLACKBALL); +} + +bool unblackball(const std::string &filename, const crypto::public_key &output) +{ + return blackball_worker(filename, output, BLACKBALL_UNBLACKBALL); +} + +bool blackballed(const std::string &filename, const crypto::public_key &output) +{ + return blackball_worker(filename, output, BLACKBALL_QUERY); +} + +bool clear_blackballs(const std::string &filename) +{ + return blackball_worker(filename, crypto::public_key(), BLACKBALL_CLEAR); +} + }} diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 3edb57804..5aea47075 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -41,5 +41,10 @@ namespace tools bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); + + bool blackball(const std::string &filename, const crypto::public_key &output); + bool unblackball(const std::string &filename, const crypto::public_key &output); + bool blackballed(const std::string &filename, const crypto::public_key &output); + bool clear_blackballs(const std::string &filename); } } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6c71a81db..9cdb842ca 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5541,16 +5541,43 @@ bool wallet2::find_and_save_rings(const std::string &filename, bool force) return true; } -bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const +bool wallet2::blackball_output(const crypto::public_key &output) +{ + return ringdb::blackball(get_ring_database(), output); +} + +bool wallet2::set_blackballed_outputs(const std::vector &outputs, bool add) +{ + bool ret = true; + if (!add) + ret &= ringdb::clear_blackballs(get_ring_database()); + for (const auto &output: outputs) + ret &= ringdb::blackball(get_ring_database(), output); + return ret; +} + +bool wallet2::unblackball_output(const crypto::public_key &output) +{ + return ringdb::unblackball(get_ring_database(), output); +} + +bool wallet2::is_output_blackballed(const crypto::public_key &output) const +{ + return ringdb::blackballed(get_ring_database(), output); +} + +bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const { if (!unlocked) // don't add locked outs return false; if (global_index == real_index) // don't re-add real one return false; - auto item = std::make_tuple(global_index, tx_public_key, mask); + auto item = std::make_tuple(global_index, output_public_key, mask); CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; + if (is_output_blackballed(output_public_key)) // don't add blackballed outputs + return false; outs.back().push_back(item); return true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 979372851..e446da3fd 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1058,6 +1058,11 @@ namespace tools bool get_ring(const std::string &filename, const crypto::key_image &key_image, std::vector &outs); bool find_and_save_rings(const std::string &filename, bool force = true); + bool blackball_output(const crypto::public_key &output); + bool set_blackballed_outputs(const std::vector &outputs, bool add = false); + bool unblackball_output(const crypto::public_key &output); + bool is_output_blackballed(const crypto::public_key &output) const; + private: /*! * \brief Stores wallet information to wallet file. -- cgit v1.2.3 From db10dd6d8329de92e212247a2eb101c773d4c3cd Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 27 Feb 2018 08:30:59 +0000 Subject: wallet: make ringdb an object with database state --- src/blockchain_utilities/blockchain_blackball.cpp | 10 +- src/simplewallet/simplewallet.cpp | 4 +- src/wallet/ringdb.cpp | 223 ++++++++++------------ src/wallet/ringdb.h | 32 +++- src/wallet/wallet2.cpp | 72 ++++--- src/wallet/wallet2.h | 16 +- 6 files changed, 187 insertions(+), 170 deletions(-) (limited to 'src/wallet/ringdb.cpp') diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index 485b9d0bd..d065d61cb 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -310,6 +310,7 @@ int main(int argc, char* argv[]) std::unordered_map> relative_rings; std::unordered_map> outputs; std::unordered_set spent, newly_spent; + tools::ringdb ringdb(output_file_path.string()); for (size_t n = 0; n < inputs.size(); ++n) { @@ -335,7 +336,7 @@ int main(int argc, char* argv[]) { const crypto::public_key pkey = core_storage[n]->get_output_key(txin.amount, txin.key_offsets[0]); MINFO("Blackballing output " << pkey << ", due to being used in a 1-ring"); - tools::ringdb::blackball(output_file_path.string(), pkey); + ringdb.blackball(pkey); newly_spent.insert(output_data(txin.amount, txin.key_offsets[0])); spent.insert(output_data(txin.amount, txin.key_offsets[0])); } @@ -363,7 +364,7 @@ int main(int argc, char* argv[]) { const crypto::public_key pkey = core_storage[n]->get_output_key(txin.amount, common[0]); MINFO("Blackballing output " << pkey << ", due to being used in rings with a single common element"); - tools::ringdb::blackball(output_file_path.string(), pkey); + ringdb.blackball(pkey); newly_spent.insert(output_data(txin.amount, common[0])); spent.insert(output_data(txin.amount, common[0])); } @@ -407,8 +408,9 @@ int main(int argc, char* argv[]) if (known == absolute.size() - 1) { const crypto::public_key pkey = core_storage[0]->get_output_key(od.amount, last_unknown); - MINFO("Blackballing output " << pkey << ", due to being used in rings where all other outputs are known to be spent"); - tools::ringdb::blackball(output_file_path.string(), pkey); + MINFO("Blackballing output " << pkey << ", due to being used in a " << + absolute.size() << "-ring where all other outputs are known to be spent"); + ringdb.blackball(pkey); newly_spent.insert(output_data(od.amount, last_unknown)); spent.insert(output_data(od.amount, last_unknown)); } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 315d91aa6..89edee3fe 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1312,7 +1312,7 @@ bool simple_wallet::print_ring(const std::vector &args) std::vector ring; try { - if (m_wallet->get_ring(m_wallet->get_ring_database(), key_image, ring)) + if (m_wallet->get_ring(key_image, ring)) { std::stringstream str; for (const auto &x: ring) @@ -1466,7 +1466,7 @@ bool simple_wallet::save_known_rings(const std::vector &args) try { LOCK_IDLE_SCOPE(); - m_wallet->find_and_save_rings(m_wallet->get_ring_database()); + m_wallet->find_and_save_rings(); } catch (const std::exception &e) { diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 198312dc4..bd2b4453b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -172,115 +172,64 @@ static size_t get_ring_data_size(size_t n_entries) enum { BLACKBALL_BLACKBALL, BLACKBALL_UNBLACKBALL, BLACKBALL_QUERY, BLACKBALL_CLEAR}; -static bool blackball_worker(const std::string &filename, const crypto::public_key &output, int op) +namespace tools +{ + +ringdb::ringdb(std::string filename): + filename(filename) { - MDB_env *env; - MDB_dbi dbi; MDB_txn *txn; - MDB_cursor *cursor; - int dbr; bool tx_active = false; - bool ret = true; + int dbr; - if (filename.empty()) - return true; tools::create_directories_if_necessary(filename); dbr = mdb_env_create(&env); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_set_maxdbs(env, 1); + dbr = mdb_env_set_maxdbs(env, 2); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); - dbr = resize_env(env, filename.c_str(), 32 * 2); // a pubkey, and some slack - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + const std::string actual_filename = get_rings_filename(filename); + dbr = mdb_env_open(env, actual_filename.c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file '" + + actual_filename + "': " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - dbr = mdb_dbi_open(txn, "blackballs", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); - mdb_set_dupsort(txn, dbi, compare_hash32); - MDB_val key = zerokeyval; - MDB_val data; - data.mv_data = (void*)&output; - data.mv_size = sizeof(output); + dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi_rings); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + mdb_set_compare(txn, dbi_rings, compare_hash32); - switch (op) - { - case BLACKBALL_BLACKBALL: - MDEBUG("Blackballing output " << output); - dbr = mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA); - if (dbr == MDB_KEYEXIST) - dbr = 0; - break; - case BLACKBALL_UNBLACKBALL: - MDEBUG("Unblackballing output " << output); - dbr = mdb_del(txn, dbi, &key, &data); - if (dbr == MDB_NOTFOUND) - dbr = 0; - break; - case BLACKBALL_QUERY: - MDEBUG("Querying blackball status for output " << output); - dbr = mdb_cursor_open(txn, dbi, &cursor); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); - dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); - MDEBUG("Querying blackball status for output " << output << ": " << std::string(mdb_strerror(dbr))); - THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); - ret = dbr != MDB_NOTFOUND; - if (dbr == MDB_NOTFOUND) - dbr = 0; - mdb_cursor_close(cursor); - break; - case BLACKBALL_CLEAR: - dbr = mdb_drop(txn, dbi, 0); - break; - default: - THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); - } - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); + dbr = mdb_dbi_open(txn, "blackballs", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + mdb_set_dupsort(txn, dbi_blackballs, compare_hash32); dbr = mdb_txn_commit(txn); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn blackballing output to database: " + std::string(mdb_strerror(dbr))); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn creating/opening database: " + std::string(mdb_strerror(dbr))); tx_active = false; - return ret; } -namespace tools { namespace ringdb +ringdb::~ringdb() { + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); +} -bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) { - MDB_env *env; - MDB_dbi dbi; MDB_txn *txn; int dbr; bool tx_active = false; - if (filename.empty()) - return true; - tools::create_directories_if_necessary(filename); - - dbr = mdb_env_create(&env); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_set_maxdbs(env, 1); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); dbr = resize_env(env, filename.c_str(), get_ring_data_size(tx.vin.size())); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); - mdb_set_compare(txn, dbi, compare_hash32); for (const auto &in: tx.vin) { @@ -301,7 +250,7 @@ bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key std::string data_ciphertext = encrypt(compressed_ring, txin.k_image, chacha_key); data.mv_size = data_ciphertext.size(); data.mv_data = (void*)data_ciphertext.c_str(); - dbr = mdb_put(txn, dbi, &key, &data, 0); + dbr = mdb_put(txn, dbi_rings, &key, &data, 0); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to add ring to database: " + std::string(mdb_strerror(dbr))); } @@ -311,35 +260,18 @@ bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key return true; } -bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) { - MDB_env *env; - MDB_dbi dbi; MDB_txn *txn; int dbr; bool tx_active = false; - if (filename.empty()) - return true; - tools::create_directories_if_necessary(filename); - - dbr = mdb_env_create(&env); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_set_maxdbs(env, 1); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); dbr = resize_env(env, filename.c_str(), 0); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); - mdb_set_compare(txn, dbi, compare_hash32); for (const auto &in: tx.vin) { @@ -355,14 +287,14 @@ bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_ key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); - dbr = mdb_get(txn, dbi, &key, &data); + dbr = mdb_get(txn, dbi_rings, &key, &data); THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); if (dbr == MDB_NOTFOUND) continue; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); MDEBUG("Removing ring data for key image " << txin.k_image); - dbr = mdb_del(txn, dbi, &key, NULL); + dbr = mdb_del(txn, dbi_rings, &key, NULL); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to remove ring to database: " + std::string(mdb_strerror(dbr))); } @@ -372,41 +304,24 @@ bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_ return true; } -bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs) +bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs) { - MDB_env *env; - MDB_dbi dbi; MDB_txn *txn; int dbr; bool tx_active = false; - if (filename.empty()) - return false; - tools::create_directories_if_necessary(filename); - - dbr = mdb_env_create(&env); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_set_maxdbs(env, 1); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); - dbr = mdb_env_open(env, get_rings_filename(filename).c_str(), 0, 0664); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller env_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_env_close(env);}); dbr = resize_env(env, filename.c_str(), 0); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); - epee::misc_utils::auto_scope_leave_caller dbi_dtor = epee::misc_utils::create_scope_leave_handler([&](){mdb_dbi_close(env, dbi);}); - mdb_set_compare(txn, dbi, compare_hash32); MDB_val key, data; std::string key_ciphertext = encrypt(key_image, chacha_key); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); - dbr = mdb_get(txn, dbi, &key, &data); + dbr = mdb_get(txn, dbi_rings, &key, &data); THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); if (dbr == MDB_NOTFOUND) return false; @@ -425,24 +340,82 @@ bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, return true; } -bool blackball(const std::string &filename, const crypto::public_key &output) +bool ringdb::blackball_worker(const crypto::public_key &output, int op) { - return blackball_worker(filename, output, BLACKBALL_BLACKBALL); + MDB_txn *txn; + MDB_cursor *cursor; + int dbr; + bool tx_active = false; + bool ret = true; + + dbr = resize_env(env, filename.c_str(), 32 * 2); // a pubkey, and some slack + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + MDB_val key = zerokeyval; + MDB_val data; + data.mv_data = (void*)&output; + data.mv_size = sizeof(output); + + switch (op) + { + case BLACKBALL_BLACKBALL: + MDEBUG("Blackballing output " << output); + dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_NODUPDATA); + if (dbr == MDB_KEYEXIST) + dbr = 0; + break; + case BLACKBALL_UNBLACKBALL: + MDEBUG("Unblackballing output " << output); + dbr = mdb_del(txn, dbi_blackballs, &key, &data); + if (dbr == MDB_NOTFOUND) + dbr = 0; + break; + case BLACKBALL_QUERY: + dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); + dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); + ret = dbr != MDB_NOTFOUND; + if (dbr == MDB_NOTFOUND) + dbr = 0; + mdb_cursor_close(cursor); + break; + case BLACKBALL_CLEAR: + dbr = mdb_drop(txn, dbi_blackballs, 0); + break; + default: + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); + } + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn blackballing output to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return ret; } -bool unblackball(const std::string &filename, const crypto::public_key &output) +bool ringdb::blackball(const crypto::public_key &output) { - return blackball_worker(filename, output, BLACKBALL_UNBLACKBALL); + return blackball_worker(output, BLACKBALL_BLACKBALL); } -bool blackballed(const std::string &filename, const crypto::public_key &output) +bool ringdb::unblackball(const crypto::public_key &output) { - return blackball_worker(filename, output, BLACKBALL_QUERY); + return blackball_worker(output, BLACKBALL_UNBLACKBALL); } -bool clear_blackballs(const std::string &filename) +bool ringdb::blackballed(const crypto::public_key &output) { - return blackball_worker(filename, crypto::public_key(), BLACKBALL_CLEAR); + return blackball_worker(output, BLACKBALL_QUERY); } -}} +bool ringdb::clear_blackballs() +{ + return blackball_worker(crypto::public_key(), BLACKBALL_CLEAR); +} + +} diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 5aea47075..351ae5a2b 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -30,21 +30,35 @@ #include #include +#include #include "wipeable_string.h" #include "crypto/crypto.h" #include "cryptonote_basic/cryptonote_basic.h" namespace tools { - namespace ringdb + class ringdb { - bool add_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); - bool remove_rings(const std::string &filename, const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); - bool get_ring(const std::string &filename, const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); + public: + ringdb(std::string filename); + ~ringdb(); - bool blackball(const std::string &filename, const crypto::public_key &output); - bool unblackball(const std::string &filename, const crypto::public_key &output); - bool blackballed(const std::string &filename, const crypto::public_key &output); - bool clear_blackballs(const std::string &filename); - } + bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); + + bool blackball(const crypto::public_key &output); + bool unblackball(const crypto::public_key &output); + bool blackballed(const crypto::public_key &output); + bool clear_blackballs(); + + private: + bool blackball_worker(const crypto::public_key &output, int op); + + private: + std::string filename; + MDB_env *env; + MDB_dbi dbi_rings; + MDB_dbi dbi_blackballs; + }; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9cdb842ca..732dc0789 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -675,7 +675,12 @@ wallet2::wallet2(network_type nettype, bool restricted): m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), m_key_on_device(false), - m_ring_history_saved(false) + m_ring_history_saved(false), + m_ringdb() +{ +} + +wallet2::~wallet2() { } @@ -1520,7 +1525,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans entry.first->second.m_timestamp = ts; entry.first->second.m_unlock_time = tx.unlock_time; - add_rings(get_ring_database(), tx); + add_rings(tx); } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices) @@ -1891,7 +1896,7 @@ void wallet2::update_pool_state(bool refreshed) pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; // the inputs aren't spent anymore, since the tx failed - remove_rings(m_ring_database, pit->second.m_tx); + remove_rings(pit->second.m_tx); for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) { if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) @@ -3793,7 +3798,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass try { - find_and_save_rings(get_ring_database(), false); + find_and_save_rings(false); } catch (const std::exception &e) { @@ -4540,7 +4545,7 @@ void wallet2::commit_tx(pending_tx& ptx) m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); } - add_rings(m_ring_database, ptx.tx); + add_rings(ptx.tx); LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); @@ -5448,44 +5453,55 @@ void wallet2::set_ring_database(const std::string &filename) { m_ring_database = filename; MINFO("ringdb path set to " << filename); + m_ringdb.reset(); + if (!m_ring_database.empty()) + m_ringdb.reset(new tools::ringdb(m_ring_database)); } -bool wallet2::add_rings(const std::string &filename, const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx) +bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx) { - return ringdb::add_rings(filename, key, tx); + if (!m_ringdb) + return true; + return m_ringdb->add_rings(key, tx); } -bool wallet2::add_rings(const std::string &filename, const cryptonote::transaction_prefix &tx) +bool wallet2::add_rings(const cryptonote::transaction_prefix &tx) { crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return add_rings(filename, key, tx); + return add_rings(key, tx); } -bool wallet2::remove_rings(const std::string &filename, const cryptonote::transaction_prefix &tx) +bool wallet2::remove_rings(const cryptonote::transaction_prefix &tx) { + if (!m_ringdb) + return true; crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return ringdb::remove_rings(filename, key, tx); + return m_ringdb->remove_rings(key, tx); } -bool wallet2::get_ring(const std::string &filename, const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs) +bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs) { - return ringdb::get_ring(filename, key, key_image, outs); + if (!m_ringdb) + return true; + return m_ringdb->get_ring(key, key_image, outs); } -bool wallet2::get_ring(const std::string &filename, const crypto::key_image &key_image, std::vector &outs) +bool wallet2::get_ring(const crypto::key_image &key_image, std::vector &outs) { crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return get_ring(filename, key, key_image, outs); + return get_ring(key, key_image, outs); } -bool wallet2::find_and_save_rings(const std::string &filename, bool force) +bool wallet2::find_and_save_rings(bool force) { if (!force && m_ring_history_saved) return true; + if (!m_ringdb) + return true; COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); @@ -5533,7 +5549,7 @@ bool wallet2::find_and_save_rings(const std::string &filename, bool force) crypto::hash tx_hash, tx_prefix_hash; THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); THROW_WALLET_EXCEPTION_IF(epee::string_tools::pod_to_hex(tx_hash) != tx_info.tx_hash, error::wallet_internal_error, "txid mismatch"); - THROW_WALLET_EXCEPTION_IF(!add_rings(filename, key, tx), error::wallet_internal_error, "Failed to save ring"); + THROW_WALLET_EXCEPTION_IF(!add_rings(key, tx), error::wallet_internal_error, "Failed to save ring"); } MINFO("Found and saved rings for " << res.txs.size() << " transactions"); @@ -5543,27 +5559,35 @@ bool wallet2::find_and_save_rings(const std::string &filename, bool force) bool wallet2::blackball_output(const crypto::public_key &output) { - return ringdb::blackball(get_ring_database(), output); + if (!m_ringdb) + return true; + return m_ringdb->blackball(output); } bool wallet2::set_blackballed_outputs(const std::vector &outputs, bool add) { + if (!m_ringdb) + return true; bool ret = true; if (!add) - ret &= ringdb::clear_blackballs(get_ring_database()); + ret &= m_ringdb->clear_blackballs(); for (const auto &output: outputs) - ret &= ringdb::blackball(get_ring_database(), output); + ret &= m_ringdb->blackball(output); return ret; } bool wallet2::unblackball_output(const crypto::public_key &output) { - return ringdb::unblackball(get_ring_database(), output); + if (!m_ringdb) + return true; + return m_ringdb->unblackball(output); } bool wallet2::is_output_blackballed(const crypto::public_key &output) const { - return ringdb::blackballed(get_ring_database(), output); + if (!m_ringdb) + return true; + return m_ringdb->blackballed(output); } bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const @@ -5880,7 +5904,7 @@ void wallet2::get_outs(std::vector> if (td.m_key_image_known && !td.m_key_image_partial) { std::vector ring; - if (get_ring(get_ring_database(), key, td.m_key_image, ring)) + if (get_ring(key, td.m_key_image, ring)) { MINFO("This output has a known ring, reusing (size " << ring.size() << ")"); THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error, @@ -6072,7 +6096,7 @@ void wallet2::get_outs(std::vector> if (td.m_key_image_known && !td.m_key_image_partial) { std::vector ring; - if (get_ring(get_ring_database(), key, td.m_key_image, ring)) + if (get_ring(key, td.m_key_image, ring)) { for (uint64_t out: ring) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e446da3fd..c62bdbfc8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -66,6 +66,8 @@ class Serialization_portability_wallet_Test; namespace tools { + class ringdb; + class i_wallet2_callback { public: @@ -166,6 +168,7 @@ namespace tools static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev); wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, bool restricted = false); + ~wallet2(); struct multisig_info { @@ -1055,8 +1058,8 @@ namespace tools void set_ring_database(const std::string &filename); const std::string get_ring_database() const { return m_ring_database; } - bool get_ring(const std::string &filename, const crypto::key_image &key_image, std::vector &outs); - bool find_and_save_rings(const std::string &filename, bool force = true); + bool get_ring(const crypto::key_image &key_image, std::vector &outs); + bool find_and_save_rings(bool force = true); bool blackball_output(const crypto::public_key &output); bool set_blackballed_outputs(const std::vector &outputs, bool add = false); @@ -1119,10 +1122,10 @@ namespace tools rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; rct::key get_multisig_k(size_t idx, const std::unordered_set &used_L) const; void update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n); - bool add_rings(const std::string &filename, const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); - bool add_rings(const std::string &filename, const cryptonote::transaction_prefix &tx); - bool remove_rings(const std::string &filename, const cryptonote::transaction_prefix &tx); - bool get_ring(const std::string &filename, const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs); + bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); + bool add_rings(const cryptonote::transaction_prefix &tx); + bool remove_rings(const cryptonote::transaction_prefix &tx); + bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs); bool get_output_distribution(uint64_t &start_height, std::vector &distribution); @@ -1213,6 +1216,7 @@ namespace tools std::string m_ring_database; bool m_ring_history_saved; + std::unique_ptr m_ringdb; }; } BOOST_CLASS_VERSION(tools::wallet2, 24) -- cgit v1.2.3 From b09e5181cc7c24fc6cf13b4a43044833bd2763fa Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 28 Feb 2018 18:26:06 +0000 Subject: wallet: add a set_ring command This is so one can set rings for spent key images in case the attackers don't merge the ring matching patch set. --- src/simplewallet/simplewallet.cpp | 81 ++++++++++++++++++++++++++++++++++++++- src/simplewallet/simplewallet.h | 1 + src/wallet/ringdb.cpp | 30 +++++++++++++++ src/wallet/ringdb.h | 1 + src/wallet/wallet2.cpp | 11 ++++++ src/wallet/wallet2.h | 1 + 6 files changed, 124 insertions(+), 1 deletion(-) (limited to 'src/wallet/ringdb.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 89edee3fe..61a234317 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1317,7 +1317,7 @@ bool simple_wallet::print_ring(const std::vector &args) std::stringstream str; for (const auto &x: ring) str << x << " "; - success_msg_writer() << tr("Ring size ") << std::to_string(ring.size()) << ": " << str.str(); + success_msg_writer() << tr("Ring size ") << std::to_string(ring.size()) << ": " << str.str() << tr(" (absolute)"); } else { @@ -1332,6 +1332,81 @@ bool simple_wallet::print_ring(const std::vector &args) return true; } +bool simple_wallet::set_ring(const std::vector &args) +{ + crypto::key_image key_image; + if (args.size() < 3) + { + fail_msg_writer() << tr("usage: set_ring absolute|relative [...]"); + return true; + } + + if (!epee::string_tools::hex_to_pod(args[0], key_image)) + { + fail_msg_writer() << tr("Invalid key image"); + return true; + } + + bool relative; + if (args[1] == "absolute") + { + relative = false; + } + else if (args[1] == "relative") + { + relative = true; + } + else + { + fail_msg_writer() << tr("Missing absolute or relative keyword"); + return true; + } + + std::vector ring; + for (size_t n = 2; n < args.size(); ++n) + { + ring.resize(ring.size() + 1); + if (!string_tools::get_xtype_from_string(ring.back(), args[n])) + { + fail_msg_writer() << tr("invalid index: must be a strictly positive unsigned integer"); + return true; + } + if (relative) + { + if (ring.size() > 1 && !ring.back()) + { + fail_msg_writer() << tr("invalid index: must be a strictly positive unsigned integer"); + return true; + } + uint64_t sum = 0; + for (uint64_t out: ring) + { + if (out > std::numeric_limits::max() - sum) + { + fail_msg_writer() << tr("invalid index: indices wrap"); + return true; + } + sum += out; + } + } + else + { + if (ring.size() > 1 && ring[ring.size() - 2] >= ring[ring.size() - 1]) + { + fail_msg_writer() << tr("invalid index: indices should be in strictly ascending order"); + return true; + } + } + } + if (!m_wallet->set_ring(key_image, ring, relative)) + { + fail_msg_writer() << tr("failed to set ring"); + return true; + } + + return true; +} + bool simple_wallet::blackball(const std::vector &args) { crypto::public_key output; @@ -2166,6 +2241,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::print_ring, this, _1), tr("print_ring "), tr("Print the ring used to spend a given key image (if the ring size is > 1)")); + m_cmd_binder.set_handler("set_ring", + boost::bind(&simple_wallet::set_ring, this, _1), + tr("set_ring absolute|relative [...]"), + tr("Set the ring used for a given key image, so it can be reused in a fork")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), tr("save_known_rings"), diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 372057022..c69df817d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -211,6 +211,7 @@ namespace cryptonote bool submit_multisig(const std::vector& args); bool export_raw_multisig(const std::vector& args); bool print_ring(const std::vector& args); + bool set_ring(const std::vector& args); bool save_known_rings(const std::vector& args); bool blackball(const std::vector& args); bool unblackball(const std::vector& args); diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index bd2b4453b..d2ed90e51 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -340,6 +340,36 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } +bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative) +{ + MDB_txn *txn; + int dbr; + bool tx_active = false; + + dbr = resize_env(env, filename.c_str(), outs.size() * 64); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + MDB_val key, data; + std::string key_ciphertext = encrypt(key_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + std::string compressed_ring = compress_ring(relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs)); + std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); + data.mv_size = data_ciphertext.size(); + data.mv_data = (void*)data_ciphertext.c_str(); + dbr = mdb_put(txn, dbi_rings, &key, &data, 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set ring for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + bool ringdb::blackball_worker(const crypto::public_key &output, int op) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 351ae5a2b..fb6b732a9 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -46,6 +46,7 @@ namespace tools bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); + bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative); bool blackball(const crypto::public_key &output); bool unblackball(const crypto::public_key &output); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 732dc0789..b2cbd416b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5496,6 +5496,17 @@ bool wallet2::get_ring(const crypto::key_image &key_image, std::vector return get_ring(key, key_image, outs); } +bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector &outs, bool relative) +{ + if (!m_ringdb) + return true; + + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + + return m_ringdb->set_ring(key, key_image, outs, relative); +} + bool wallet2::find_and_save_rings(bool force) { if (!force && m_ring_history_saved) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c62bdbfc8..acccc6ca8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1059,6 +1059,7 @@ namespace tools void set_ring_database(const std::string &filename); const std::string get_ring_database() const { return m_ring_database; } bool get_ring(const crypto::key_image &key_image, std::vector &outs); + bool set_ring(const crypto::key_image &key_image, const std::vector &outs, bool relative); bool find_and_save_rings(bool force = true); bool blackball_output(const crypto::public_key &output); -- cgit v1.2.3 From 504428ab4a7aa7773832acdc3de0baad22d6b9b7 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 4 Mar 2018 13:30:40 +0000 Subject: ringdb: use the genesis block as a db name This will avoid careless forkers polluting the shared database even if they make their own chain. They'll then automatically start using another subdb, and any key-reusing fork of those forks will reuse their subdbs. --- src/blockchain_utilities/blockchain_blackball.cpp | 4 +++- src/wallet/ringdb.cpp | 6 +++--- src/wallet/ringdb.h | 2 +- src/wallet/wallet2.cpp | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) (limited to 'src/wallet/ringdb.cpp') diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index d065d61cb..40ce898d9 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -310,7 +310,9 @@ int main(int argc, char* argv[]) std::unordered_map> relative_rings; std::unordered_map> outputs; std::unordered_set spent, newly_spent; - tools::ringdb ringdb(output_file_path.string()); + + cryptonote::block b = core_storage[0]->get_db().get_block_from_height(0); + tools::ringdb ringdb(output_file_path.string(), epee::string_tools::pod_to_hex(get_block_hash(b))); for (size_t n = 0; n < inputs.size(); ++n) { diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index d2ed90e51..e4d3df30c 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -175,7 +175,7 @@ enum { BLACKBALL_BLACKBALL, BLACKBALL_UNBLACKBALL, BLACKBALL_QUERY, BLACKBALL_CL namespace tools { -ringdb::ringdb(std::string filename): +ringdb::ringdb(std::string filename, const std::string &genesis): filename(filename) { MDB_txn *txn; @@ -198,11 +198,11 @@ ringdb::ringdb(std::string filename): epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - dbr = mdb_dbi_open(txn, "rings", MDB_CREATE, &dbi_rings); + dbr = mdb_dbi_open(txn, ("rings-" + genesis).c_str(), MDB_CREATE, &dbi_rings); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); mdb_set_compare(txn, dbi_rings, compare_hash32); - dbr = mdb_dbi_open(txn, "blackballs", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); + dbr = mdb_dbi_open(txn, ("blackballs-" + genesis).c_str(), MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); mdb_set_dupsort(txn, dbi_blackballs, compare_hash32); diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index fb6b732a9..2bd1ac149 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -40,7 +40,7 @@ namespace tools class ringdb { public: - ringdb(std::string filename); + ringdb(std::string filename, const std::string &genesis); ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b2cbd416b..3d28e80ef 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5454,8 +5454,10 @@ void wallet2::set_ring_database(const std::string &filename) m_ring_database = filename; MINFO("ringdb path set to " << filename); m_ringdb.reset(); + cryptonote::block b; + generate_genesis(b); if (!m_ring_database.empty()) - m_ringdb.reset(new tools::ringdb(m_ring_database)); + m_ringdb.reset(new tools::ringdb(m_ring_database, epee::string_tools::pod_to_hex(get_block_hash(b)))); } bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx) -- cgit v1.2.3 From d32ef7b0f25af2d7617da375b30fa76dae3affd1 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 10 Mar 2018 14:18:33 +0000 Subject: ringdb: factor ring addition code --- src/wallet/ringdb.cpp | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) (limited to 'src/wallet/ringdb.cpp') diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index e4d3df30c..44992520f 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -126,6 +126,20 @@ static std::string decrypt(const std::string &ciphertext, const crypto::key_imag return plaintext; } +static void store_relative_ring(MDB_txn *txn, MDB_dbi &dbi, const crypto::key_image &key_image, const std::vector &relative_ring, const crypto::chacha_key &chacha_key) +{ + MDB_val key, data; + std::string key_ciphertext = encrypt(key_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + std::string compressed_ring = compress_ring(relative_ring); + std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); + data.mv_size = data_ciphertext.size(); + data.mv_data = (void*)data_ciphertext.c_str(); + int dbr = mdb_put(txn, dbi, &key, &data, 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set ring for key image in LMDB table: " + std::string(mdb_strerror(dbr))); +} + static int resize_env(MDB_env *env, const char *db_path, size_t needed) { MDB_envinfo mei; @@ -240,18 +254,7 @@ bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::t if (ring_size == 1) continue; - MDB_val key, data; - std::string key_ciphertext = encrypt(txin.k_image, chacha_key); - key.mv_data = (void*)key_ciphertext.data(); - key.mv_size = key_ciphertext.size(); - MDEBUG("Saving relative ring for key image " << txin.k_image << ": " << - boost::join(txin.key_offsets | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); - std::string compressed_ring = compress_ring(txin.key_offsets); - std::string data_ciphertext = encrypt(compressed_ring, txin.k_image, chacha_key); - data.mv_size = data_ciphertext.size(); - data.mv_data = (void*)data_ciphertext.c_str(); - dbr = mdb_put(txn, dbi_rings, &key, &data, 0); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to add ring to database: " + std::string(mdb_strerror(dbr))); + store_relative_ring(txn, dbi_rings, txin.k_image, txin.key_offsets, chacha_key); } dbr = mdb_txn_commit(txn); @@ -353,16 +356,7 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); - key.mv_data = (void*)key_ciphertext.data(); - key.mv_size = key_ciphertext.size(); - std::string compressed_ring = compress_ring(relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs)); - std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); - data.mv_size = data_ciphertext.size(); - data.mv_data = (void*)data_ciphertext.c_str(); - dbr = mdb_put(txn, dbi_rings, &key, &data, 0); - THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set ring for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key); dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr))); -- cgit v1.2.3