record blackballs as amount/offset, and add export ability

This commit is contained in:
moneromooo-monero 2018-08-29 16:57:12 +00:00
parent 4bce935b40
commit 44439c3208
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
7 changed files with 287 additions and 103 deletions

View file

@ -66,17 +66,17 @@ static MDB_env *env = NULL;
struct output_data struct output_data
{ {
uint64_t amount; uint64_t amount;
uint64_t index; uint64_t offset;
output_data(): amount(0), index(0) {} output_data(): amount(0), offset(0) {}
output_data(uint64_t a, uint64_t i): amount(a), index(i) {} output_data(uint64_t a, uint64_t i): amount(a), offset(i) {}
bool operator==(const output_data &other) const { return other.amount == amount && other.index == index; } bool operator==(const output_data &other) const { return other.amount == amount && other.offset == offset; }
}; };
// //
// relative_rings: key_image -> vector<uint64_t> // relative_rings: key_image -> vector<uint64_t>
// outputs: 128 bits -> set of key images // outputs: 128 bits -> set of key images
// processed_txidx: string -> uint64_t // processed_txidx: string -> uint64_t
// spent: 128 bits, zerokval // spent: amount -> offset
// ring_instances: vector<uint64_t> -> uint64_t // ring_instances: vector<uint64_t> -> uint64_t
// stats: string -> arbitrary // stats: string -> arbitrary
// //
@ -263,7 +263,7 @@ static void init(std::string cache_filename)
dbr = mdb_dbi_open(txn, "spent", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_spent); dbr = mdb_dbi_open(txn, "spent", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_spent);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_dupsort(txn, dbi_spent, compare_double64); mdb_set_dupsort(txn, dbi_spent, compare_uint64);
dbr = mdb_dbi_open(txn, "ring_instances", MDB_CREATE, &dbi_ring_instances); dbr = mdb_dbi_open(txn, "ring_instances", MDB_CREATE, &dbi_ring_instances);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
@ -511,13 +511,19 @@ static uint64_t get_num_spent_outputs()
dbr = mdb_cursor_open(txn, dbi_spent, &cur); dbr = mdb_cursor_open(txn, dbi_spent, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for spent outputs: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for spent outputs: " + std::string(mdb_strerror(dbr)));
MDB_val k, v; MDB_val k, v;
mdb_size_t count = 0; mdb_size_t count = 0, tmp;
dbr = mdb_cursor_get(cur, &k, &v, MDB_FIRST);
if (dbr != MDB_NOTFOUND) MDB_cursor_op op = MDB_FIRST;
while (1)
{ {
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get first spent output: " + std::string(mdb_strerror(dbr))); dbr = mdb_cursor_get(cur, &k, &v, op);
dbr = mdb_cursor_count(cur, &count); op = MDB_NEXT_NODUP;
if (dbr == MDB_NOTFOUND)
break;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get first/next spent output: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_count(cur, &tmp);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to count entries: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to count entries: " + std::string(mdb_strerror(dbr)));
count += tmp;
} }
mdb_cursor_close(cur); mdb_cursor_close(cur);
@ -530,15 +536,17 @@ static uint64_t get_num_spent_outputs()
static void add_spent_output(MDB_cursor *cur, const output_data &od) static void add_spent_output(MDB_cursor *cur, const output_data &od)
{ {
MDB_val v = {sizeof(od), (void*)&od}; MDB_val k = {sizeof(od.amount), (void*)&od.amount};
int dbr = mdb_cursor_put(cur, (MDB_val *)&zerokval, &v, MDB_NODUPDATA); MDB_val v = {sizeof(od.offset), (void*)&od.offset};
int dbr = mdb_cursor_put(cur, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_KEYEXIST, "Failed to add spent output: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_KEYEXIST, "Failed to add spent output: " + std::string(mdb_strerror(dbr)));
} }
static bool is_output_spent(MDB_cursor *cur, const output_data &od) static bool is_output_spent(MDB_cursor *cur, const output_data &od)
{ {
MDB_val v = {sizeof(od), (void*)&od}; MDB_val k = {sizeof(od.amount), (void*)&od.amount};
int dbr = mdb_cursor_get(cur, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); MDB_val v = {sizeof(od.offset), (void*)&od.offset};
int dbr = mdb_cursor_get(cur, &k, &v, MDB_GET_BOTH);
CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_NOTFOUND, "Failed to get spent output: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_NOTFOUND, "Failed to get spent output: " + std::string(mdb_strerror(dbr)));
bool spent = dbr == 0; bool spent = dbr == 0;
return spent; return spent;
@ -562,8 +570,7 @@ static std::vector<output_data> get_spent_outputs(MDB_txn *txn)
outs.reserve(count); outs.reserve(count);
while (1) while (1)
{ {
const output_data *od = (const output_data*)v.mv_data; outs.push_back({*(const uint64_t*)k.mv_data, *(const uint64_t*)v.mv_data});
outs.push_back(*od);
dbr = mdb_cursor_get(cur, &k, &v, MDB_NEXT); dbr = mdb_cursor_get(cur, &k, &v, MDB_NEXT);
if (dbr == MDB_NOTFOUND) if (dbr == MDB_NOTFOUND)
break; break;
@ -815,14 +822,6 @@ static void close_db(MDB_env *env, MDB_txn *txn, MDB_cursor *cur, MDB_dbi dbi)
mdb_env_close(env); mdb_env_close(env);
} }
static crypto::public_key get_output_key(MDB_cursor *cur, uint64_t amount, uint64_t offset)
{
MDB_val k = { sizeof(amount), (void*)&amount }, v = { sizeof(offset), (void*)&offset };
int dbr = mdb_cursor_get(cur, &k, &v, MDB_GET_BOTH);
if (dbr) throw std::runtime_error("Output key not found: " + std::string(mdb_strerror(dbr)));
return *(const crypto::public_key*)(((const char*)v.mv_data) + sizeof(uint64_t) * 2);
}
static void get_num_outputs(MDB_txn *txn, MDB_cursor *cur, MDB_dbi dbi, uint64_t &pre_rct, uint64_t &rct) static void get_num_outputs(MDB_txn *txn, MDB_cursor *cur, MDB_dbi dbi, uint64_t &pre_rct, uint64_t &rct)
{ {
uint64_t amount = 0; uint64_t amount = 0;
@ -884,6 +883,113 @@ static crypto::hash get_genesis_block_hash(const std::string &filename)
return genesis_block_hash; return genesis_block_hash;
} }
static std::vector<std::pair<uint64_t, uint64_t>> load_outputs(const std::string &filename)
{
std::vector<std::pair<uint64_t, uint64_t>> outputs;
uint64_t amount = std::numeric_limits<uint64_t>::max();
FILE *f;
f = fopen(filename.c_str(), "r");
if (!f)
{
MERROR("Failed to load outputs from " << filename << ": " << strerror(errno));
return {};
}
while (1)
{
char s[256];
fgets(s, sizeof(s), f);
if (feof(f))
break;
const size_t len = strlen(s);
if (len > 0 && s[len - 1] == '\n')
s[len - 1] = 0;
if (!s[0])
continue;
std::pair<uint64_t, uint64_t> output;
uint64_t offset, num_offsets;
if (sscanf(s, "@%" PRIu64, &amount) == 1)
{
continue;
}
if (amount == std::numeric_limits<uint64_t>::max())
{
MERROR("Bad format in " << filename);
continue;
}
if (sscanf(s, "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets < std::numeric_limits<uint64_t>::max() - offset)
{
while (num_offsets-- > 0)
outputs.push_back(std::make_pair(amount, offset++));
}
else if (sscanf(s, "%" PRIu64, &offset) == 1)
{
outputs.push_back(std::make_pair(amount, offset));
}
else
{
MERROR("Bad format in " << filename);
continue;
}
}
fclose(f);
return outputs;
}
static bool export_spent_outputs(MDB_cursor *cur, const std::string &filename)
{
FILE *f = fopen(filename.c_str(), "w");
if (!f)
{
MERROR("Failed to open " << filename << ": " << strerror(errno));
return false;
}
uint64_t pending_amount = std::numeric_limits<uint64_t>::max();
std::vector<uint64_t> pending_offsets;
MDB_val k, v;
MDB_cursor_op op = MDB_FIRST;
while (1)
{
int dbr = mdb_cursor_get(cur, &k, &v, op);
if (dbr == MDB_NOTFOUND)
break;
op = MDB_NEXT;
if (dbr)
{
fclose(f);
MERROR("Failed to enumerate spent outputs: " << mdb_strerror(dbr));
return false;
}
const uint64_t amount = *(const uint64_t*)k.mv_data;
const uint64_t offset = *(const uint64_t*)v.mv_data;
if (!pending_offsets.empty() && (amount != pending_amount || pending_offsets.back()+1 != offset))
{
if (pending_offsets.size() == 1)
fprintf(f, "%" PRIu64 "\n", pending_offsets.front());
else
fprintf(f, "%" PRIu64 "*%" PRIu64 "\n", pending_offsets.front(), pending_offsets.size());
pending_offsets.clear();
}
if (pending_amount != amount)
{
fprintf(f, "@%" PRIu64 "\n", amount);
pending_amount = amount;
}
pending_offsets.push_back(offset);
}
if (!pending_offsets.empty())
{
if (pending_offsets.size() == 1)
fprintf(f, "%" PRIu64 "\n", pending_offsets.front());
else
fprintf(f, "%" PRIu64 "*%" PRIu64 "\n", pending_offsets.front(), pending_offsets.size());
pending_offsets.clear();
}
fclose(f);
return true;
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
TRY_ENTRY(); TRY_ENTRY();
@ -920,6 +1026,8 @@ int main(int argc, char* argv[])
, "Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]." , "Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]."
, "fast:1000" , "fast:1000"
}; };
const command_line::arg_descriptor<std::string> arg_extra_spent_list = {"extra-spent-list", "Optional list of known spent outputs",""};
const command_line::arg_descriptor<std::string> arg_export = {"export", "Filename to export the backball list to"};
command_line::add_arg(desc_cmd_sett, arg_blackball_db_dir); command_line::add_arg(desc_cmd_sett, arg_blackball_db_dir);
command_line::add_arg(desc_cmd_sett, arg_log_level); command_line::add_arg(desc_cmd_sett, arg_log_level);
@ -928,6 +1036,8 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_cmd_sett, arg_check_subsets); command_line::add_arg(desc_cmd_sett, arg_check_subsets);
command_line::add_arg(desc_cmd_sett, arg_verbose); command_line::add_arg(desc_cmd_sett, arg_verbose);
command_line::add_arg(desc_cmd_sett, arg_db_sync_mode); command_line::add_arg(desc_cmd_sett, arg_db_sync_mode);
command_line::add_arg(desc_cmd_sett, arg_extra_spent_list);
command_line::add_arg(desc_cmd_sett, arg_export);
command_line::add_arg(desc_cmd_sett, arg_inputs); command_line::add_arg(desc_cmd_sett, arg_inputs);
command_line::add_arg(desc_cmd_only, command_line::arg_help); command_line::add_arg(desc_cmd_only, command_line::arg_help);
@ -967,6 +1077,9 @@ int main(int argc, char* argv[])
bool opt_rct_only = command_line::get_arg(vm, arg_rct_only); bool opt_rct_only = command_line::get_arg(vm, arg_rct_only);
bool opt_check_subsets = command_line::get_arg(vm, arg_check_subsets); bool opt_check_subsets = command_line::get_arg(vm, arg_check_subsets);
bool opt_verbose = command_line::get_arg(vm, arg_verbose); bool opt_verbose = command_line::get_arg(vm, arg_verbose);
std::string opt_export = command_line::get_arg(vm, arg_export);
std::string extra_spent_list = command_line::get_arg(vm, arg_extra_spent_list);
std::vector<std::pair<uint64_t, uint64_t>> extra_spent_outputs = extra_spent_list.empty() ? std::vector<std::pair<uint64_t, uint64_t>>() : load_outputs(extra_spent_list);
std::string db_type = command_line::get_arg(vm, arg_database); std::string db_type = command_line::get_arg(vm, arg_database);
if (!cryptonote::blockchain_valid_db_type(db_type)) if (!cryptonote::blockchain_valid_db_type(db_type))
@ -1015,6 +1128,36 @@ int main(int argc, char* argv[])
MDB_cursor *cur0; MDB_cursor *cur0;
open_db(inputs[0], &env0, &txn0, &cur0, &dbi0); open_db(inputs[0], &env0, &txn0, &cur0, &dbi0);
if (!extra_spent_outputs.empty())
{
MINFO("Adding " << extra_spent_outputs.size() << " extra spent outputs");
MDB_txn *txn;
int dbr = mdb_txn_begin(env, NULL, 0, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
MDB_cursor *cur;
dbr = mdb_cursor_open(txn, dbi_spent, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr)));
std::vector<std::pair<uint64_t, uint64_t>> blackballs;
for (const std::pair<uint64_t, uint64_t> &output: extra_spent_outputs)
{
if (!is_output_spent(cur, output_data(output.first, output.second)))
{
blackballs.push_back(output);
add_spent_output(cur, output_data(output.first, output.second));
inc_stat(txn, output.first ? "pre-rct-extra" : "rct-ring-extra");
}
}
if (!blackballs.empty())
{
ringdb.blackball(blackballs);
blackballs.clear();
}
mdb_cursor_close(cur);
dbr = mdb_txn_commit(txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to commit txn creating/opening database: " + std::string(mdb_strerror(dbr)));
}
for (size_t n = 0; n < inputs.size(); ++n) for (size_t n = 0; n < inputs.size(); ++n)
{ {
const std::string canonical = boost::filesystem::canonical(inputs[n]).string(); const std::string canonical = boost::filesystem::canonical(inputs[n]).string();
@ -1033,7 +1176,7 @@ int main(int argc, char* argv[])
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr)));
size_t records = 0; size_t records = 0;
const std::string filename = inputs[n]; const std::string filename = inputs[n];
std::vector<crypto::public_key> blackballs; std::vector<std::pair<uint64_t, uint64_t>> blackballs;
uint64_t n_txes; uint64_t n_txes;
for_all_transactions(filename, start_idx, n_txes, [&](const cryptonote::transaction_prefix &tx)->bool for_all_transactions(filename, start_idx, n_txes, [&](const cryptonote::transaction_prefix &tx)->bool
{ {
@ -1057,13 +1200,13 @@ int main(int argc, char* argv[])
const uint64_t instances = inc_ring_instances(txn, txin.amount, new_ring); const uint64_t instances = inc_ring_instances(txn, txin.amount, new_ring);
if (n == 0 && ring_size == 1) if (n == 0 && ring_size == 1)
{ {
const crypto::public_key pkey = get_output_key(cur0, txin.amount, absolute[0]); const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[0]);
if (opt_verbose) if (opt_verbose)
{ {
MINFO("Blackballing output " << pkey << ", due to being used in a 1-ring"); MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a 1-ring");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
} }
blackballs.push_back(pkey); blackballs.push_back(output);
add_spent_output(cur, output_data(txin.amount, absolute[0])); add_spent_output(cur, output_data(txin.amount, absolute[0]));
inc_stat(txn, txin.amount ? "pre-rct-ring-size-1" : "rct-ring-size-1"); inc_stat(txn, txin.amount ? "pre-rct-ring-size-1" : "rct-ring-size-1");
} }
@ -1071,13 +1214,13 @@ int main(int argc, char* argv[])
{ {
for (size_t o = 0; o < new_ring.size(); ++o) for (size_t o = 0; o < new_ring.size(); ++o)
{ {
const crypto::public_key pkey = get_output_key(cur0, txin.amount, absolute[o]); const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose) if (opt_verbose)
{ {
MINFO("Blackballing output " << pkey << ", due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings"); MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
} }
blackballs.push_back(pkey); blackballs.push_back(output);
add_spent_output(cur, output_data(txin.amount, absolute[o])); add_spent_output(cur, output_data(txin.amount, absolute[o]));
inc_stat(txn, txin.amount ? "pre-rct-duplicate-rings" : "rct-duplicate-rings"); inc_stat(txn, txin.amount ? "pre-rct-duplicate-rings" : "rct-duplicate-rings");
} }
@ -1086,13 +1229,13 @@ int main(int argc, char* argv[])
{ {
for (size_t o = 0; o < new_ring.size(); ++o) for (size_t o = 0; o < new_ring.size(); ++o)
{ {
const crypto::public_key pkey = get_output_key(cur0, txin.amount, absolute[o]); const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose) if (opt_verbose)
{ {
MINFO("Blackballing output " << pkey << ", due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings"); MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
} }
blackballs.push_back(pkey); blackballs.push_back(output);
add_spent_output(cur, output_data(txin.amount, absolute[o])); add_spent_output(cur, output_data(txin.amount, absolute[o]));
inc_stat(txn, txin.amount ? "pre-rct-subset-rings" : "rct-subset-rings"); inc_stat(txn, txin.amount ? "pre-rct-subset-rings" : "rct-subset-rings");
} }
@ -1122,13 +1265,13 @@ int main(int argc, char* argv[])
} }
else if (common.size() == 1) else if (common.size() == 1)
{ {
const crypto::public_key pkey = get_output_key(cur0, txin.amount, common[0]); const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, common[0]);
if (opt_verbose) if (opt_verbose)
{ {
MINFO("Blackballing output " << pkey << ", due to being used in rings with a single common element"); MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in rings with a single common element");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush; std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
} }
blackballs.push_back(pkey); blackballs.push_back(output);
add_spent_output(cur, output_data(txin.amount, common[0])); add_spent_output(cur, output_data(txin.amount, common[0]));
inc_stat(txn, txin.amount ? "pre-rct-key-image-attack" : "rct-key-image-attack"); inc_stat(txn, txin.amount ? "pre-rct-key-image-attack" : "rct-key-image-attack");
} }
@ -1211,7 +1354,7 @@ int main(int argc, char* argv[])
dbr = mdb_cursor_open(txn, dbi_spent, &cur); dbr = mdb_cursor_open(txn, dbi_spent, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr))); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr)));
std::vector<crypto::public_key> blackballs; std::vector<std::pair<uint64_t, uint64_t>> blackballs;
std::vector<output_data> scan_spent = std::move(work_spent); std::vector<output_data> scan_spent = std::move(work_spent);
work_spent.clear(); work_spent.clear();
for (const output_data &od: scan_spent) for (const output_data &od: scan_spent)
@ -1234,13 +1377,13 @@ int main(int argc, char* argv[])
} }
if (known == absolute.size() - 1) if (known == absolute.size() - 1)
{ {
const crypto::public_key pkey = get_output_key(cur0, od.amount, last_unknown); const std::pair<uint64_t, uint64_t> output = std::make_pair(od.amount, last_unknown);
if (opt_verbose) if (opt_verbose)
{ {
MINFO("Blackballing output " << pkey << ", due to being used in a " << MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a " <<
absolute.size() << "-ring where all other outputs are known to be spent"); absolute.size() << "-ring where all other outputs are known to be spent");
} }
blackballs.push_back(pkey); blackballs.push_back(output);
add_spent_output(cur, output_data(od.amount, last_unknown)); add_spent_output(cur, output_data(od.amount, last_unknown));
work_spent.push_back(output_data(od.amount, last_unknown)); work_spent.push_back(output_data(od.amount, last_unknown));
inc_stat(txn, od.amount ? "pre-rct-chain-reaction" : "rct-chain-reaction"); inc_stat(txn, od.amount ? "pre-rct-chain-reaction" : "rct-chain-reaction");
@ -1279,6 +1422,7 @@ skip_secondary_passes:
{ "pre-rct-duplicate-rings", pre_rct }, { "rct-duplicate-rings", rct }, { "pre-rct-duplicate-rings", pre_rct }, { "rct-duplicate-rings", rct },
{ "pre-rct-subset-rings", pre_rct }, { "rct-subset-rings", rct }, { "pre-rct-subset-rings", pre_rct }, { "rct-subset-rings", rct },
{ "pre-rct-key-image-attack", pre_rct }, { "rct-key-image-attack", rct }, { "pre-rct-key-image-attack", pre_rct }, { "rct-key-image-attack", rct },
{ "pre-rct-extra", pre_rct }, { "rct-ring-extra", rct },
{ "pre-rct-chain-reaction", pre_rct }, { "rct-chain-reaction", rct }, { "pre-rct-chain-reaction", pre_rct }, { "rct-chain-reaction", rct },
}; };
for (const auto &key: stat_keys) for (const auto &key: stat_keys)
@ -1291,6 +1435,19 @@ skip_secondary_passes:
} }
mdb_txn_abort(txn); mdb_txn_abort(txn);
if (!opt_export.empty())
{
MDB_txn *txn;
int dbr = mdb_txn_begin(env, NULL, 0, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
MDB_cursor *cur;
dbr = mdb_cursor_open(txn, dbi_spent, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr)));
export_spent_outputs(cur, opt_export);
mdb_cursor_close(cur);
mdb_txn_abort(txn);
}
LOG_PRINT_L0("Blockchain blackball data exported OK"); LOG_PRINT_L0("Blockchain blackball data exported OK");
close_db(env0, txn0, cur0, dbi0); close_db(env0, txn0, cur0, dbi0);
close(); close();

View file

@ -1624,23 +1624,23 @@ bool simple_wallet::set_ring(const std::vector<std::string> &args)
bool simple_wallet::blackball(const std::vector<std::string> &args) bool simple_wallet::blackball(const std::vector<std::string> &args)
{ {
crypto::public_key output; uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets;
if (args.size() == 0) if (args.size() == 0)
{ {
fail_msg_writer() << tr("usage: blackball <output_public_key> | <filename> [add]"); fail_msg_writer() << tr("usage: blackball <amount>/<offset> | <filename> [add]");
return true; return true;
} }
try try
{ {
if (epee::string_tools::hex_to_pod(args[0], output)) if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &amount, &offset) == 2)
{ {
m_wallet->blackball_output(output); m_wallet->blackball_output(std::make_pair(amount, offset));
} }
else if (epee::file_io_utils::is_file_exist(args[0])) else if (epee::file_io_utils::is_file_exist(args[0]))
{ {
std::vector<crypto::public_key> outputs; std::vector<std::pair<uint64_t, uint64_t>> outputs;
char str[65]; char str[256];
std::unique_ptr<FILE, tools::close_file> f(fopen(args[0].c_str(), "r")); std::unique_ptr<FILE, tools::close_file> f(fopen(args[0].c_str(), "r"));
if (f) if (f)
@ -1654,10 +1654,27 @@ bool simple_wallet::blackball(const std::vector<std::string> &args)
str[len - 1] = 0; str[len - 1] = 0;
if (!str[0]) if (!str[0])
continue; continue;
outputs.push_back(crypto::public_key()); if (sscanf(str, "@%" PRIu64, &amount) == 1)
if (!epee::string_tools::hex_to_pod(str, outputs.back()))
{ {
fail_msg_writer() << tr("Invalid public key: ") << str; continue;
}
if (amount == std::numeric_limits<uint64_t>::max())
{
fail_msg_writer() << tr("First line is not an amount");
return true;
}
if (sscanf(str, "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets <= std::numeric_limits<uint64_t>::max() - offset)
{
while (num_offsets--)
outputs.push_back(std::make_pair(amount, offset++));
}
else if (sscanf(str, "%" PRIu64, &offset) == 1)
{
outputs.push_back(std::make_pair(amount, offset));
}
else
{
fail_msg_writer() << tr("Invalid output: ") << str;
return true; return true;
} }
} }
@ -1682,7 +1699,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args)
} }
else else
{ {
fail_msg_writer() << tr("Invalid public key, and file doesn't exist"); fail_msg_writer() << tr("Invalid output key, and file doesn't exist");
return true; return true;
} }
} }
@ -1696,16 +1713,16 @@ bool simple_wallet::blackball(const std::vector<std::string> &args)
bool simple_wallet::unblackball(const std::vector<std::string> &args) bool simple_wallet::unblackball(const std::vector<std::string> &args)
{ {
crypto::public_key output; std::pair<uint64_t, uint64_t> output;
if (args.size() != 1) if (args.size() != 1)
{ {
fail_msg_writer() << tr("usage: unblackball <output_public_key>"); fail_msg_writer() << tr("usage: unblackball <amount>/<offset>");
return true; return true;
} }
if (!epee::string_tools::hex_to_pod(args[0], output)) if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &output.first, &output.second) != 2)
{ {
fail_msg_writer() << tr("Invalid public key"); fail_msg_writer() << tr("Invalid output");
return true; return true;
} }
@ -1723,25 +1740,25 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args)
bool simple_wallet::blackballed(const std::vector<std::string> &args) bool simple_wallet::blackballed(const std::vector<std::string> &args)
{ {
crypto::public_key output; std::pair<uint64_t, uint64_t> output;
if (args.size() != 1) if (args.size() != 1)
{ {
fail_msg_writer() << tr("usage: blackballed <output_public_key>"); fail_msg_writer() << tr("usage: blackballed <amount>/<offset>");
return true; return true;
} }
if (!epee::string_tools::hex_to_pod(args[0], output)) if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &output.first, &output.second) != 2)
{ {
fail_msg_writer() << tr("Invalid public key"); fail_msg_writer() << tr("Invalid output");
return true; return true;
} }
try try
{ {
if (m_wallet->is_output_blackballed(output)) if (m_wallet->is_output_blackballed(output))
message_writer() << tr("Blackballed: ") << output; message_writer() << tr("Blackballed: ") << output.first << "/" << output.second;
else else
message_writer() << tr("not blackballed: ") << output; message_writer() << tr("not blackballed: ") << output.first << "/" << output.second;
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@ -2554,15 +2571,15 @@ simple_wallet::simple_wallet()
tr("Save known rings to the shared rings database")); tr("Save known rings to the shared rings database"));
m_cmd_binder.set_handler("blackball", m_cmd_binder.set_handler("blackball",
boost::bind(&simple_wallet::blackball, this, _1), boost::bind(&simple_wallet::blackball, this, _1),
tr("blackball <output public key> | <filename> [add]"), tr("blackball <amount>/<offset> | <filename> [add]"),
tr("Blackball output(s) so they never get selected as fake outputs in a ring")); tr("Blackball output(s) so they never get selected as fake outputs in a ring"));
m_cmd_binder.set_handler("unblackball", m_cmd_binder.set_handler("unblackball",
boost::bind(&simple_wallet::unblackball, this, _1), boost::bind(&simple_wallet::unblackball, this, _1),
tr("unblackball <output public key>"), tr("unblackball <amount>/<offset>"),
tr("Unblackballs an output so it may get selected as a fake output in a ring")); tr("Unblackballs an output so it may get selected as a fake output in a ring"));
m_cmd_binder.set_handler("blackballed", m_cmd_binder.set_handler("blackballed",
boost::bind(&simple_wallet::blackballed, this, _1), boost::bind(&simple_wallet::blackballed, this, _1),
tr("blackballed <output public key>"), tr("blackballed <amount>/<offset>"),
tr("Checks whether an output is blackballed")); tr("Checks whether an output is blackballed"));
m_cmd_binder.set_handler("version", m_cmd_binder.set_handler("version",
boost::bind(&simple_wallet::version, this, _1), boost::bind(&simple_wallet::version, this, _1),

View file

@ -55,6 +55,13 @@ static int compare_hash32(const MDB_val *a, const MDB_val *b)
return 0; return 0;
} }
static int compare_uint64(const MDB_val *a, const MDB_val *b)
{
const uint64_t va = *(const uint64_t*) a->mv_data;
const uint64_t vb = *(const uint64_t*) b->mv_data;
return va < vb ? -1 : va > vb;
}
static std::string compress_ring(const std::vector<uint64_t> &ring) static std::string compress_ring(const std::vector<uint64_t> &ring)
{ {
std::string s; std::string s;
@ -217,9 +224,9 @@ ringdb::ringdb(std::string filename, const std::string &genesis):
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); 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); mdb_set_compare(txn, dbi_rings, compare_hash32);
dbr = mdb_dbi_open(txn, ("blackballs-" + genesis).c_str(), MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); dbr = mdb_dbi_open(txn, ("blackballs2-" + 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))); 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); mdb_set_dupsort(txn, dbi_blackballs, compare_uint64);
dbr = mdb_txn_commit(txn); dbr = mdb_txn_commit(txn);
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn creating/opening 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)));
@ -374,7 +381,7 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im
return true; return true;
} }
bool ringdb::blackball_worker(const std::vector<crypto::public_key> &outputs, int op) bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op)
{ {
MDB_txn *txn; MDB_txn *txn;
MDB_cursor *cursor; MDB_cursor *cursor;
@ -391,24 +398,25 @@ bool ringdb::blackball_worker(const std::vector<crypto::public_key> &outputs, in
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); 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; tx_active = true;
MDB_val key = zerokeyval;
MDB_val data;
for (const crypto::public_key &output: outputs) MDB_val key, data;
for (const std::pair<uint64_t, uint64_t> &output: outputs)
{ {
data.mv_data = (void*)&output; key.mv_data = (void*)&output.first;
data.mv_size = sizeof(output); key.mv_size = sizeof(output.first);
data.mv_data = (void*)&output.second;
data.mv_size = sizeof(output.second);
switch (op) switch (op)
{ {
case BLACKBALL_BLACKBALL: case BLACKBALL_BLACKBALL:
MDEBUG("Blackballing output " << output); MDEBUG("Blackballing output " << output.first << "/" << output.second);
dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_NODUPDATA); dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_APPENDDUP);
if (dbr == MDB_KEYEXIST) if (dbr == MDB_KEYEXIST)
dbr = 0; dbr = 0;
break; break;
case BLACKBALL_UNBLACKBALL: case BLACKBALL_UNBLACKBALL:
MDEBUG("Unblackballing output " << output); MDEBUG("Unblackballing output " << output.first << "/" << output.second);
dbr = mdb_del(txn, dbi_blackballs, &key, &data); dbr = mdb_del(txn, dbi_blackballs, &key, &data);
if (dbr == MDB_NOTFOUND) if (dbr == MDB_NOTFOUND)
dbr = 0; dbr = 0;
@ -443,32 +451,32 @@ bool ringdb::blackball_worker(const std::vector<crypto::public_key> &outputs, in
return ret; return ret;
} }
bool ringdb::blackball(const std::vector<crypto::public_key> &outputs) bool ringdb::blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs)
{ {
return blackball_worker(outputs, BLACKBALL_BLACKBALL); return blackball_worker(outputs, BLACKBALL_BLACKBALL);
} }
bool ringdb::blackball(const crypto::public_key &output) bool ringdb::blackball(const std::pair<uint64_t, uint64_t> &output)
{ {
std::vector<crypto::public_key> outputs(1, output); std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output);
return blackball_worker(outputs, BLACKBALL_BLACKBALL); return blackball_worker(outputs, BLACKBALL_BLACKBALL);
} }
bool ringdb::unblackball(const crypto::public_key &output) bool ringdb::unblackball(const std::pair<uint64_t, uint64_t> &output)
{ {
std::vector<crypto::public_key> outputs(1, output); std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output);
return blackball_worker(outputs, BLACKBALL_UNBLACKBALL); return blackball_worker(outputs, BLACKBALL_UNBLACKBALL);
} }
bool ringdb::blackballed(const crypto::public_key &output) bool ringdb::blackballed(const std::pair<uint64_t, uint64_t> &output)
{ {
std::vector<crypto::public_key> outputs(1, output); std::vector<std::pair<uint64_t, uint64_t>> outputs(1, output);
return blackball_worker(outputs, BLACKBALL_QUERY); return blackball_worker(outputs, BLACKBALL_QUERY);
} }
bool ringdb::clear_blackballs() bool ringdb::clear_blackballs()
{ {
return blackball_worker(std::vector<crypto::public_key>(), BLACKBALL_CLEAR); return blackball_worker(std::vector<std::pair<uint64_t, uint64_t>>(), BLACKBALL_CLEAR);
} }
} }

View file

@ -49,14 +49,14 @@ namespace tools
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 set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); 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); bool blackball(const std::pair<uint64_t, uint64_t> &output);
bool blackball(const std::vector<crypto::public_key> &outputs); bool blackball(const std::vector<std::pair<uint64_t, uint64_t>> &outputs);
bool unblackball(const crypto::public_key &output); bool unblackball(const std::pair<uint64_t, uint64_t> &output);
bool blackballed(const crypto::public_key &output); bool blackballed(const std::pair<uint64_t, uint64_t> &output);
bool clear_blackballs(); bool clear_blackballs();
private: private:
bool blackball_worker(const std::vector<crypto::public_key> &outputs, int op); bool blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, int op);
private: private:
std::string filename; std::string filename;

View file

@ -6221,7 +6221,7 @@ bool wallet2::find_and_save_rings(bool force)
return true; return true;
} }
bool wallet2::blackball_output(const crypto::public_key &output) bool wallet2::blackball_output(const std::pair<uint64_t, uint64_t> &output)
{ {
if (!m_ringdb) if (!m_ringdb)
return false; return false;
@ -6229,7 +6229,7 @@ bool wallet2::blackball_output(const crypto::public_key &output)
catch (const std::exception &e) { return false; } catch (const std::exception &e) { return false; }
} }
bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add) bool wallet2::set_blackballed_outputs(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, bool add)
{ {
if (!m_ringdb) if (!m_ringdb)
return false; return false;
@ -6244,7 +6244,7 @@ bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &out
catch (const std::exception &e) { return false; } catch (const std::exception &e) { return false; }
} }
bool wallet2::unblackball_output(const crypto::public_key &output) bool wallet2::unblackball_output(const std::pair<uint64_t, uint64_t> &output)
{ {
if (!m_ringdb) if (!m_ringdb)
return false; return false;
@ -6252,7 +6252,7 @@ bool wallet2::unblackball_output(const crypto::public_key &output)
catch (const std::exception &e) { return false; } catch (const std::exception &e) { return false; }
} }
bool wallet2::is_output_blackballed(const crypto::public_key &output) const bool wallet2::is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const
{ {
if (!m_ringdb) if (!m_ringdb)
return false; return false;
@ -6297,8 +6297,8 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out
CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); 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 if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
return false; return false;
if (is_output_blackballed(output_public_key)) // don't add blackballed outputs // if (is_output_blackballed(output_public_key)) // don't add blackballed outputs
return false; // return false;
outs.back().push_back(item); outs.back().push_back(item);
return true; return true;
} }
@ -6795,6 +6795,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if (seen_indices.count(i)) if (seen_indices.count(i))
continue; continue;
if (is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs
continue;
seen_indices.emplace(i); seen_indices.emplace(i);
LOG_PRINT_L2("picking " << i << " as " << type); LOG_PRINT_L2("picking " << i << " as " << type);

View file

@ -1161,10 +1161,10 @@ namespace tools
bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative);
bool find_and_save_rings(bool force = true); bool find_and_save_rings(bool force = true);
bool blackball_output(const crypto::public_key &output); bool blackball_output(const std::pair<uint64_t, uint64_t> &output);
bool set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add = false); bool set_blackballed_outputs(const std::vector<std::pair<uint64_t, uint64_t>> &outputs, bool add = false);
bool unblackball_output(const crypto::public_key &output); bool unblackball_output(const std::pair<uint64_t, uint64_t> &output);
bool is_output_blackballed(const crypto::public_key &output) const; bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const;
bool lock_keys_file(); bool lock_keys_file();
bool unlock_keys_file(); bool unlock_keys_file();

View file

@ -59,17 +59,17 @@ static crypto::key_image generate_key_image()
return key_image; return key_image;
} }
static crypto::public_key generate_output() static std::pair<uint64_t, uint64_t> generate_output()
{ {
return rct::rct2pk(rct::scalarmultBase(rct::skGen())); return std::make_pair(rand(), rand());
} }
static const crypto::chacha_key KEY_1 = generate_chacha_key(); static const crypto::chacha_key KEY_1 = generate_chacha_key();
static const crypto::chacha_key KEY_2 = generate_chacha_key(); static const crypto::chacha_key KEY_2 = generate_chacha_key();
static const crypto::key_image KEY_IMAGE_1 = generate_key_image(); static const crypto::key_image KEY_IMAGE_1 = generate_key_image();
static const crypto::public_key OUTPUT_1 = generate_output(); static const std::pair<uint64_t, uint64_t> OUTPUT_1 = generate_output();
static const crypto::public_key OUTPUT_2 = generate_output(); static const std::pair<uint64_t, uint64_t> OUTPUT_2 = generate_output();
class RingDB: public tools::ringdb class RingDB: public tools::ringdb
{ {