New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RELEASE] Several mitigations for attacking privacy via key reusing forks #3322
[RELEASE] Several mitigations for attacking privacy via key reusing forks #3322
Conversation
I've just thought there should be a "set_ring" command, in case the forkers don't merge this. I'll do this momentarily. |
I can't speak for the code itself, but I support the general approach. It boils down to three things:
Of course, a disclaimer that if a large proportion of transactions (for ringsize 5 >33%; ringsize 7 >50%) on the forked chain don't take advantage of 1, then there is a relatively likely chance that the real input can be revealed. For ringsize 5, suppose 50% of initial transactions on a fork use this tool. Then, approx. 6.25% of other transactions will be compromised (total 53%, since 50%*6.25%) on the fork chain. Suppose half of the transactions on Monero are related to the fork, then the proportion of compromised inputs on the Monero chain falls to approx. 53/2 = 27%. These are terribly rough estimates, but even small proportions of people using this tool help the forked chain to a small extent and Monero to a larger extent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, although my confidence is particularly low regarding the get_outs
function which is quite long.
src/cryptonote_core/blockchain.h
Outdated
/** | ||
* @brief gets per block distribution of rct outputs | ||
* | ||
* @return amount the amount to get a ditribution for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@param
src/cryptonote_core/blockchain.cpp
Outdated
|
||
distribution.clear(); | ||
uint64_t db_height = m_db->height(); | ||
distribution.resize(db_height - start_height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize with zero: distribution.resize(db_height - start_height, 0);
uint32_t rpc_version; | ||
boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); | ||
// no error | ||
if (!!result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the design of NodeRPCProxy
a bit strange, because all the query methods always return boost::optional<std::string>()
which equals to boost::none
. So this section is always no-op.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns an optional error string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also expect so, but all the return statement I see in node_rpc_proxy.cpp is always return boost::optional<std::string>();
. Or am I missing places where such an optional error string gets returned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Various calls to CHECK_AND_ASSERT_MES, which typically return resp_t.result.status.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, sorry I was completely overlooking them.
src/wallet/wallet2.cpp
Outdated
req.params.amounts.push_back(0); | ||
m_daemon_rpc_mutex.lock(); | ||
MDEBUG("calling get_output_distribution"); | ||
bool r = net_utils::invoke_http_json("/json_rpc", req, res, m_http_client, rpc_timeout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invoke_http_json_rpc
can be used instead to eliminate boilerplate lines like req.jsonrpc = "2.0";
.
@@ -36,6 +36,7 @@ | |||
#include <vector> | |||
|
|||
#include "cryptonote_basic/cryptonote_format_utils.h" | |||
#include "cryptonote_core/cryptonote_tx_utils.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in ringdb.cpp instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wallet_errors.h uses cryptonote::tx_source_entry in tx_not_constructed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. The code has been compiling fine merely because of the specific order wallet_errors.h was included, I suppose.
src/simplewallet/simplewallet.cpp
Outdated
m_cmd_binder.set_handler("blackball", | ||
boost::bind(&simple_wallet::blackball, this, _1), | ||
tr("blackball <output public key> | <filename> [<add>]"), | ||
tr("Blackball an output so it never gets selected as a fake output in a ring")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an output (or a set of outputs in a file)
|
||
po::options_description desc_cmd_only("Command line options"); | ||
po::options_description desc_cmd_sett("Command line options and settings options"); | ||
const command_line::arg_descriptor<std::string> arg_blackball_db_dir = {"blackball-db-dir", "Specify blackball database directory", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this could use the dependent argument as in #3170?
continue; | ||
|
||
const std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(txin.key_offsets); | ||
if (n == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only for the n==0
case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a fork, there'll be (amount,index) duplicates. You want to use the main db only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. It wasn't clear in the code that n==0
is supposed be the main db. I think some more words should be added to the arg description.
src/wallet/wallet2.cpp
Outdated
{ | ||
m_ring_database = filename; | ||
MGINFO("ringdb path set to " << filename); | ||
delete m_ringdb; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check if not null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete does so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, embarrassing :)
src/simplewallet/simplewallet.cpp
Outdated
} | ||
if (relative) | ||
{ | ||
if (!ring.back()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn’t this exclude the case with the first index being 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I removed some "useless code", was not useless after all :/
Some of my review comments got hidden due to push -f, but they're still unaddressed. Please take a look. |
2eaeb4f
to
7985728
Compare
src/wallet/wallet2.h
Outdated
bool remove_rings(const cryptonote::transaction_prefix &tx); | ||
bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); | ||
|
||
bool get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &offsets); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I don't see this function being used anywhere. What's the intent?
- Why is the second argument named
offsets
instead ofdistribution
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used by my patch to use a gamma distribution for fake out selection, which hasn't been PR'd. Since I picked this code from this patch, I might as well bring in the function that uses it too.
} | ||
else | ||
{ | ||
if (ring.size() > 1 && ring[ring.size() - 2] >= ring[ring.size() - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this test be performed for every index?
if (ring.size() > 1)
{
for (size_t i = 1; i < ring.size(); ++i)
{
if (ring[i - 1] >= ring[i])
{
...failure...
return true;
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's done for every index, there's a loop above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I misread.
src/simplewallet/simplewallet.cpp
Outdated
} | ||
fclose(f); | ||
bool add = false; | ||
if (args.size() > 1 && args[1] != "add") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic seems incorrect, where add
never becomes true
. My suggestion:
if (args.size() > 1)
{
if (args[1] != "add)
{
...failure...
return true;
}
add = true;
}
src/wallet/ringdb.cpp
Outdated
|
||
MDEBUG("Removing ring data for key image " << txin.k_image); | ||
dbr = mdb_del(txn, dbi_rings, &key, NULL); | ||
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to add ring to database: " + std::string(mdb_strerror(dbr))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Failed to remove ring from database: "
bool clear_blackballs(); | ||
|
||
private: | ||
bool blackball_worker(const crypto::public_key &output, int op); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like further refactoring is possible using exactly the same strategy; i.e. by introducing these internal worker functions:
bool rings_worker(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx, int op);
bool ring_worker(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs, bool relative, int op);
For the second one, the parameter outs
would act as input/output when op
is SET
/GET
. Also, I think it makes sense to have the function get_ring
accept the parameter relative
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I did those before the blackball one, and when I started doing this it created lots of conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of conflicts? Can't you just add some switches to the worker function and direct the execution flow based on the op code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a patch that I came up with:
diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp
index 522a7ca7..6b22292e 100644
--- a/src/wallet/ringdb.cpp
+++ b/src/wallet/ringdb.cpp
@@ -170,6 +170,8 @@ 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 { RINGS_ADD, RINGS_REMOVE};
+enum { RING_GET, RING_SET};
enum { BLACKBALL_BLACKBALL, BLACKBALL_UNBLACKBALL, BLACKBALL_QUERY, BLACKBALL_CLEAR};
namespace tools
@@ -218,13 +220,24 @@ ringdb::~ringdb()
mdb_env_close(env);
}
-bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx)
+bool ringdb::rings_worker(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx, int op)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
- dbr = resize_env(env, filename.c_str(), get_ring_data_size(tx.vin.size()));
+ if (op == RINGS_ADD)
+ {
+ dbr = resize_env(env, filename.c_str(), get_ring_data_size(tx.vin.size()));
+ }
+ else if (op == RINGS_REMOVE)
+ {
+ dbr = resize_env(env, filename.c_str(), 0);
+ }
+ else
+ {
+ THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid rings op");
+ }
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)));
@@ -244,130 +257,119 @@ bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::t
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)));
+
+ if (op == RINGS_ADD)
+ {
+ 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);
+ }
+ else
+ {
+ 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_rings, &key, NULL);
+ }
+ THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, std::string("Failed to ") + (op == RINGS_ADD ? "add ring to" : "remove ring from") + " 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)));
+ THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, std::string("Failed to commit txn ") + (op == RING_GET ? "adding rings to" : "removing rings from") + " database: " + std::string(mdb_strerror(dbr)));
tx_active = false;
return true;
}
+bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx)
+{
+ return rings_worker(chacha_key, tx, RINGS_ADD);
+}
+
bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx)
+{
+ return rings_worker(chacha_key, tx, RINGS_REMOVE);
+}
+
+bool ringdb::ring_worker(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs, bool relative, int op)
{
MDB_txn *txn;
int dbr;
bool tx_active = false;
- dbr = resize_env(env, filename.c_str(), 0);
- THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size");
+ if (op == RING_GET)
+ {
+ dbr = resize_env(env, filename.c_str(), 0);
+ }
+ else if (op == RING_SET)
+ {
+ dbr = resize_env(env, filename.c_str(), outs.size() * 64);
+ }
+ else
+ {
+ THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid ring op");
+ }
+ 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;
- for (const auto &in: tx.vin)
+ 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();
+ if (op == RING_GET)
{
- if (in.type() != typeid(cryptonote::txin_to_key))
- continue;
- const auto &txin = boost::get<cryptonote::txin_to_key>(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_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;
+ return false;
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_rings, &key, NULL);
- THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to add ring to database: " + std::string(mdb_strerror(dbr)));
+ 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);}), " "));
+ if (!relative)
+ {
+ outs = cryptonote::relative_output_offsets_to_absolute(outs);
+ MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
+ }
+
+ }
+ else
+ {
+ 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 adding ring to database: " + std::string(mdb_strerror(dbr)));
+ THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, std::string("Failed to commit txn ") + (op == RING_GET ? "getting ring from" : "setting ring to") + " database: " + std::string(mdb_strerror(dbr)));
tx_active = false;
return true;
}
-bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs)
+bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs, bool relative)
{
- MDB_txn *txn;
- int dbr;
- bool tx_active = false;
-
- 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;
-
- 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_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;
- 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;
+ return ring_worker(chacha_key, key_image, outs, relative, RING_GET);
}
bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &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;
+ std::vector<uint64_t> outs_copy = outs;
+ return ring_worker(chacha_key, key_image, outs_copy, relative, RING_SET);
}
bool ringdb::blackball_worker(const crypto::public_key &output, int op)
diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h
index 5cc41958..7d17421f 100644
--- a/src/wallet/ringdb.h
+++ b/src/wallet/ringdb.h
@@ -45,7 +45,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<uint64_t> &outs);
+ bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs, bool relative = false);
bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
bool blackball(const crypto::public_key &output);
@@ -54,6 +54,8 @@ namespace tools
bool clear_blackballs();
private:
+ bool rings_worker(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx, int op);
+ bool ring_worker(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs, bool relative, int op);
bool blackball_worker(const crypto::public_key &output, int op);
private:
A few duplicate lines of code in {add,remove}_rings
and {get,set}_ring
are eliminated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you are asking by "kind of conflict". Git conflicts. Did you try squashing this in the relevant commits ? You'll see the conflicts you get.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Here's my attempt in reordering and squashing commits: https://github.com/stoffu/monero/commits/pr-3322-rebase
Notable changes I had to make are:
- squash the commit turning ringdb from namespace-based to object-based
- squash the last commit introducing the
set_ring
command into the first commit introducing ringdb - separate the parameter name change in
wallet2::tx_add_fake_output
Do you think this is reasonable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks reasonable by the commit messages, but (1) I'm not PRing something I did not write unless I can ensure there's nothing fishy in it and (2) I didn't want to spend time on this before, so I'm not spending time on it now either. Maybe once I have time if this is not merged already.
src/cryptonote_core/blockchain.h
Outdated
* | ||
* @param amount the amount to get a ditribution for | ||
* @param start_height the height of the first rct output | ||
* @param distribution the start offset of the first rct output in this block (same as previous if none) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return-by-reference
to the above two params, just like other member functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's blindingly obvious, no ? But OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mostly for keeping the consistency in this header file. If existing declarations are given verbose comments, so should be the new ones, IMO.
auto it = outputs.find(od); | ||
if (it == outputs.end()) | ||
{ | ||
outputs[output_data(out.amount, indices[out.amount], coinbase, height)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps outputs[od] = {};
can make the intent a bit clearer?
} | ||
else | ||
{ | ||
it->first.info(coinbase, height); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how/why this code path can get executed, because indices
for any amount gets incremented every time od
is added to outputs
. In other words, there will never be two output_data
with the same amount and index during the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transactions are not processed in blockchain order so you can get an output used before it's created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, didn't know that.
{ | ||
if (std::find(r1.begin(), r1.end(), out) != r1.end()) | ||
common.push_back(out); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using std::set_intersection
?
std::set_intersection(r1.begin(), r1.end(), r2.begin(), r2.end(), std::back_inserter(common));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly because I did not know that existed. At least I know what the current code does. The "equivalent code" in the docs isn't immediately obviously the same as mine, so I'm keeping mine.
} | ||
else | ||
{ | ||
MINFO("The intersection has more than one element, it's still ok"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If r0
and r1
are of different lengths, ring members that aren't common in both get identified as fakes. To make use of this information, relative_rings[txin.k_image]
should be set to the smallest ring among all that share the same key image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good observation, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be set to the smallest ring among all that share the same key image.
Actually, not just the smallest among all the relevant rings, but the set intersection of all of them which can be smaller than any one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I've done, yes.
src/wallet/wallet2.cpp
Outdated
THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); | ||
if (*result != CORE_RPC_STATUS_OK) | ||
{ | ||
MDEBUG("Cannot determined daemon RPC version, not requesting rct distribution"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"determined" -> "determine"
To prevent "fix typo" commits :)
src/simplewallet/simplewallet.cpp
Outdated
m_cmd_binder.set_handler("blackballed", | ||
boost::bind(&simple_wallet::blackballed, this, _1), | ||
tr("blackballed <output public key>"), | ||
tr("Checks whether an output is blackballed")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Checks" -> "Check"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same reason as with the unblackball, just for the linguistic consistency.
7985728
to
0b65078
Compare
0b65078
to
6fe5be8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed the bug was fixed. Thanks again for developing this great tool!
src/wallet/wallet2.cpp
Outdated
num_outs = segregation_limit[amount].first; | ||
else for (const auto &he: resp_t.result.histogram) | ||
if (he.amount == amount) | ||
num_outs = he.unlocked_instances; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe break once found?
6fe5be8
to
2b02c65
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will finish later. The comments on line 358-364 in blockchain_blackball.cpp are worth pushing this review sonner than later ...
src/cryptonote_core/blockchain.cpp
Outdated
if (amount == 0) | ||
start_height = m_testnet ? testnet_hard_forks[2].height : mainnet_hard_forks[2].height; | ||
else | ||
start_height = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment indicates this is for ringct, so the comment needs to be updated or this need to be a return false. Also, this would require a ~11-12MiB vector currently. Should the height be user provided/selected? The callee already needs to know which heights are unique and important for the algorithm that is using this API to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It used to be rct only when first written, I'll change the comment. Good idea about the user being able to clamp.
src/cryptonote_core/blockchain.cpp
Outdated
if (!r) | ||
return false; | ||
for (size_t n = 1; n < distribution.size(); ++n) | ||
distribution[n] += distribution[n-1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have this logic here? Can't the callee do this if desired? Also, since these values are being serialized via varint code, so this increasing the transmission size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because that is what I needed. Admittedly not a very complicated job to leave on the caller though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it optional (cumulative bool) in the request.
LOG_PRINT_L0("No inputs given"); | ||
return 1; | ||
} | ||
std::vector<Blockchain*> core_storage(inputs.size(), NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::vector<std::unique_ptr<Blockchain>>
|
||
int main(int argc, char* argv[]) | ||
{ | ||
TRY_ENTRY(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
// tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. | ||
LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); | ||
const std::string input = command_line::get_arg(vm, arg_input); | ||
Blockchain *core_storage = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::unique_ptr
MINFO("The intersection has more than one element, it's still ok"); | ||
std::vector<uint64_t> new_ring; | ||
for (const auto &out: relative_rings[txin.k_image]) | ||
if (std::find(common.begin(), common.end(), out) != common.end()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common
contains absolute offsets, whereas relative_rings[txin.k_image]
contains relative offsets. This find is unlikely to return any useful information.
for (const auto &out: relative_rings[txin.k_image]) | ||
if (std::find(common.begin(), common.end(), out) != common.end()) | ||
new_ring.push_back(out); | ||
relative_rings[txin.k_image] = new_ring; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This write to relative_rings[txin.k_image]
is always overwritten on line 364 without any reads in-between. Presumably this is supposed to update relative_rings[txin.k_image]
so that it only contains the common elements in both rings. If that is the goal, then relative_offsets
must store absolute offsets instead of relative offsets, because the expansion algorithm will not work correctly (unless you re-packed from absolute to relative right here). There is no advantage in doing relative offsets though - that optimization is for reducing the wire/stored db size.
|
||
size_t done = 0; | ||
std::unordered_map<crypto::key_image, std::vector<uint64_t>> relative_rings; | ||
std::map<output_data, std::unordered_set<crypto::key_image>> outputs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should most likely be hash maps. They will have many elements, and are called frequently enough to notice. boost::hash<>
has built-in functionality for std::pair<>
, and so does boost::unorderded_set<>
. so the key could be std::pair<uint64_t, uint64_t>
to save on typing (if you didn't want to use boost::hash<>
with std::unordered_set
due to extra long typename). The interfaces are identical.
mutable uint64_t height; | ||
output_data(uint64_t a, uint64_t i, bool cb, uint64_t h): amount(a), index(i), coinbase(cb), height(h) {} | ||
bool operator<(const output_data &other) const | ||
{ if (amount < other.amount) return true; if (amount == other.amount && index < other.index) return true; return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could also be return std::make_pair(amount, index) < std::make_pair(other.amount, other.index);
.
while (!newly_spent.empty()) | ||
{ | ||
LOG_PRINT_L0("Secondary pass due to " << newly_spent.size() << " newly found spent outputs"); | ||
std::set<output_data> work_spent = newly_spent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
work_spend = std::move(newly_spent)
for a quick/easy perfornance gain.
f45e326
to
ef10a92
Compare
src/rpc/core_rpc_server.cpp
Outdated
for (size_t n = 1; n < distribution.size(); ++n) | ||
distribution[n] += distribution[n-1]; | ||
} | ||
res.distributions.push_back({amount, start_height, distribution, base}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::move(distribution)
No description provided.