oxen-core/src/blockchain_utilities/blockchain_blackball.cpp

1737 lines
63 KiB
C++
Raw Normal View History

// Copyright (c) 2014-2019, The Monero Project
2018-04-10 06:49:20 +02:00
// Copyright (c) 2018, The Loki 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.
#ifdef _WIN32
2021-01-04 01:09:45 +01:00
#define __STDC_FORMAT_MACROS // NOTE(oxen): Explicitly define the PRIu64 macro on Mingw
#endif
#include "common/unordered_containers_boost_serialization.h"
#include "common/command_line.h"
#include "common/string_util.h"
#include "common/varint.h"
#include "common/file.h"
#include "common/fs-format.h"
#include "common/signal_handler.h"
2020-10-23 22:32:28 +02:00
#include "common/hex.h"
#include "serialization/crypto.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_objects.h"
#include "blockchain_db/blockchain_db.h"
#include "wallet/ringdb.h"
#include "version.h"
2021-01-28 01:07:57 +01:00
#include "cryptonote_core/uptime_proof.h"
namespace po = boost::program_options;
using namespace cryptonote;
static auto logcat = log::Cat("bcutil");
static const char zerokey[8] = {0};
static const MDB_val zerokval = { sizeof(zerokey), (void *)zerokey };
static uint64_t records_per_sync = 200;
static uint64_t db_flags = 0;
static MDB_dbi dbi_relative_rings;
static MDB_dbi dbi_outputs;
static MDB_dbi dbi_processed_txidx;
static MDB_dbi dbi_spent;
static MDB_dbi dbi_per_amount;
static MDB_dbi dbi_ring_instances;
2018-08-12 11:47:03 +02:00
static MDB_dbi dbi_stats;
static MDB_env *env = NULL;
struct output_data
{
uint64_t amount;
uint64_t offset;
output_data(): amount(0), offset(0) {}
output_data(uint64_t a, uint64_t i): amount(a), offset(i) {}
bool operator==(const output_data &other) const { return other.amount == amount && other.offset == offset; }
};
//
// relative_rings: key_image -> vector<uint64_t>
// outputs: 128 bits -> set of key images
// processed_txidx: string -> uint64_t
// spent: amount -> offset
// ring_instances: vector<uint64_t> -> uint64_t
2018-08-12 11:47:03 +02:00
// stats: string -> arbitrary
//
static bool parse_db_sync_mode(std::string db_sync_mode)
{
auto options = tools::split_any(db_sync_mode, " :", true);
for(const auto &option : options)
log::debug(logcat, "option: {}", option);
// default to fast:async:1
uint64_t DEFAULT_FLAGS = DBF_FAST;
if(options.size() == 0)
{
// default to fast:async:1
db_flags = DEFAULT_FLAGS;
}
bool safemode = false;
if(options.size() >= 1)
{
if(options[0] == "safe")
{
safemode = true;
db_flags = DBF_SAFE;
}
else if(options[0] == "fast")
{
db_flags = DBF_FAST;
}
else if(options[0] == "fastest")
{
db_flags = DBF_FASTEST;
records_per_sync = 1000; // default to fastest:async:1000
}
else
db_flags = DEFAULT_FLAGS;
}
if(options.size() >= 2 && !safemode)
{
char *endptr;
std::string bpsstr{options[1]};
uint64_t bps = strtoull(bpsstr.c_str(), &endptr, 0);
if (*endptr == '\0')
records_per_sync = bps;
}
return true;
}
static fs::path get_default_db_path()
{
2021-01-04 01:09:45 +01:00
// remove .oxen, replace with .shared-ringdb
fs::path p = tools::get_default_data_dir();
p.replace_filename(".shared-ringdb");
return p;
}
static fs::path get_cache_filename(fs::path filename)
{
if (!fs::is_directory(filename))
filename.remove_filename();
return filename;
}
static int compare_hash32(const MDB_val *a, const MDB_val *b)
{
const uint32_t *va = (const uint32_t*) a->mv_data;
const uint32_t *vb = (const 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;
}
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 int compare_double64(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;
if (va == vb)
{
const uint64_t va = ((const uint64_t*) a->mv_data)[1];
const uint64_t vb = ((const uint64_t*) b->mv_data)[1];
return va < vb ? -1 : va > vb;
}
return va < vb ? -1 : va > vb;
}
static int resize_env(const char *db_path)
{
MDB_envinfo mei;
MDB_stat mst;
int ret;
size_t needed = 1000ul * 1024 * 1024; // at least 1000 MB
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;
uint64_t mapsize = mei.me_mapsize;
if (size_used + needed > mei.me_mapsize)
{
try
{
auto si = fs::space(fs::u8path(db_path));
if(si.available < needed)
{
log::error(logcat, "!! WARNING: Insufficient free space to extend database !!: {} MB available", (si.available / 1000000));
return ENOSPC;
}
}
catch(...)
{
// print something but proceed.
log::warning(logcat, "Unable to query free disk space.");
}
mapsize += needed;
}
return mdb_env_set_mapsize(env, mapsize);
}
static void init(fs::path cache_filename)
{
MDB_txn *txn;
bool tx_active = false;
int dbr;
log::info(logcat, "Creating spent output cache in {}", cache_filename);
std::error_code ec;
if (fs::create_directories(cache_filename, ec); ec)
log::warning(logcat, "Failed to create output cache directory {}: {}", cache_filename, ec.message());
int flags = 0;
if (db_flags & DBF_FAST)
flags |= MDB_NOSYNC;
if (db_flags & DBF_FASTEST)
flags |= MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC;
dbr = mdb_env_create(&env);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env, 7);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
auto actual_filename = get_cache_filename(cache_filename);
dbr = mdb_env_open(env, actual_filename.string().c_str(), flags, 0664);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open rings database file '"
+ actual_filename.string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
dbr = mdb_dbi_open(txn, "relative_rings", MDB_CREATE | MDB_INTEGERKEY, &dbi_relative_rings);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn, dbi_relative_rings, compare_hash32);
dbr = mdb_dbi_open(txn, "outputs", MDB_CREATE | MDB_INTEGERKEY, &dbi_outputs);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn, dbi_outputs, compare_double64);
dbr = mdb_dbi_open(txn, "processed_txidx", MDB_CREATE, &dbi_processed_txidx);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
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)));
mdb_set_dupsort(txn, dbi_spent, compare_uint64);
dbr = mdb_dbi_open(txn, "per_amount", MDB_CREATE | MDB_INTEGERKEY, &dbi_per_amount);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_compare(txn, dbi_per_amount, compare_uint64);
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)));
2018-08-12 11:47:03 +02:00
dbr = mdb_dbi_open(txn, "stats", MDB_CREATE, &dbi_stats);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_commit(txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to commit txn creating/opening database: " + std::string(mdb_strerror(dbr)));
tx_active = false;
}
static void close()
{
if (env)
{
mdb_dbi_close(env, dbi_relative_rings);
mdb_dbi_close(env, dbi_outputs);
mdb_dbi_close(env, dbi_processed_txidx);
mdb_dbi_close(env, dbi_per_amount);
mdb_dbi_close(env, dbi_spent);
mdb_dbi_close(env, dbi_ring_instances);
2018-08-12 11:47:03 +02:00
mdb_dbi_close(env, dbi_stats);
mdb_env_close(env);
env = NULL;
}
}
static std::string compress_ring(const std::vector<uint64_t> &ring, std::string s = "")
{
const size_t sz = s.size();
2020-06-02 05:08:48 +02:00
s.reserve(s.size() + tools::VARINT_MAX_LENGTH<uint64_t> * ring.size());
auto ins = std::back_inserter(s);
for (uint64_t out: ring)
2020-06-02 05:08:48 +02:00
tools::write_varint(ins, out);
return s;
}
static std::string compress_ring(uint64_t amount, const std::vector<uint64_t> &ring)
{
2020-06-02 05:08:48 +02:00
std::string s;
s.reserve(tools::VARINT_MAX_LENGTH<uint64_t> * (1 + ring.size()));
tools::write_varint(std::back_inserter(s), amount);
return compress_ring(ring, std::move(s));
}
static std::vector<uint64_t> decompress_ring(const std::string &s)
{
std::vector<uint64_t> 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);
CHECK_AND_ASSERT_THROW_MES(read > 0 && read <= 256, "Internal error decompressing ring");
ring.push_back(out);
}
return ring;
}
static bool for_all_transactions(const fs::path& filename, uint64_t& start_idx, uint64_t& n_txes, const std::function<bool(const cryptonote::transaction_prefix&)>& f)
{
MDB_env *env;
MDB_dbi dbi;
MDB_txn *txn;
MDB_cursor *cur;
int dbr;
bool tx_active = false;
MDB_val k;
MDB_val v;
dbr = mdb_env_create(&env);
if (dbr) throw std::runtime_error("Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env, 2);
if (dbr) throw std::runtime_error("Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_open(env, filename.string().c_str(), 0, 0664);
if (dbr) throw std::runtime_error("Failed to open rings database file '"
+ filename.u8string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
dbr = mdb_dbi_open(txn, "txs_pruned", MDB_INTEGERKEY, &dbi);
if (dbr)
dbr = mdb_dbi_open(txn, "txs", MDB_INTEGERKEY, &dbi);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn, dbi, &cur);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
MDB_stat stat;
dbr = mdb_stat(txn, dbi, &stat);
if (dbr) throw std::runtime_error("Failed to query m_block_info: " + std::string(mdb_strerror(dbr)));
n_txes = stat.ms_entries;
bool fret = true;
k.mv_size = sizeof(uint64_t);
k.mv_data = &start_idx;
MDB_cursor_op op = MDB_SET;
while (1)
{
int ret = mdb_cursor_get(cur, &k, &v, op);
op = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
throw std::runtime_error("Failed to enumerate transactions: " + std::string(mdb_strerror(ret)));
if (k.mv_size != sizeof(uint64_t))
throw std::runtime_error("Bad key size");
const uint64_t idx = *(uint64_t*)k.mv_data;
if (idx < start_idx)
continue;
cryptonote::transaction_prefix tx;
2020-06-02 05:08:48 +02:00
try {
std::string_view bd{static_cast<const char*>(v.mv_data), v.mv_size};
serialization::parse_binary(bd, tx);
} catch (const std::exception& e) {
log::error(logcat, "Failed to parse transaction from blob: {}", e.what());
2020-06-02 05:08:48 +02:00
return false;
}
start_idx = *(uint64_t*)k.mv_data;
if (!f(tx)) {
fret = false;
break;
}
}
mdb_cursor_close(cur);
Merge pull request #4459 bcf3f6af fuzz_tests: catch unhandled exceptions (moneromooo-monero) 3ebd05d4 miner: restore stream flags after changing them (moneromooo-monero) a093092e levin_protocol_handler_async: do not propagate exception through dtor (moneromooo-monero) 1eebb82b net_helper: do not propagate exceptions through dtor (moneromooo-monero) fb6a3630 miner: do not propagate exceptions through dtor (moneromooo-monero) 2e2139ff epee: do not propagate exception through dtor (moneromooo-monero) 0749a8bd db_lmdb: do not propagate exceptions in dtor (moneromooo-monero) 1b0afeeb wallet_rpc_server: exit cleanly on unhandled exceptions (moneromooo-monero) 418a9936 unit_tests: catch unhandled exceptions (moneromooo-monero) ea7f9543 threadpool: do not propagate exceptions through the dtor (moneromooo-monero) 6e855422 gen_multisig: nice exit on unhandled exception (moneromooo-monero) 53df2deb db_lmdb: catch error in mdb_stat calls during migration (moneromooo-monero) e67016dd blockchain_blackball: catch failure to commit db transaction (moneromooo-monero) 661439f4 mlog: don't remove old logs if we failed to rename the current file (moneromooo-monero) 5fdcda50 easylogging++: test for NULL before dereference (moneromooo-monero) 7ece1550 performance_test: fix bad last argument calling add_arg (moneromooo-monero) a085da32 unit_tests: add check for page size > 0 before dividing (moneromooo-monero) d8b1ec8b unit_tests: use std::shared_ptr to shut coverity up about leaks (moneromooo-monero) 02563bf4 simplewallet: top level exception catcher to print nicer messages (moneromooo-monero) c57a65b2 blockchain_blackball: fix shift range for 32 bit archs (moneromooo-monero)
2018-09-29 22:17:00 +02:00
dbr = mdb_txn_commit(txn);
if (dbr) throw std::runtime_error("Failed to commit db transaction: " + std::string(mdb_strerror(dbr)));
tx_active = false;
mdb_dbi_close(env, dbi);
mdb_env_close(env);
return fret;
}
static bool for_all_transactions(const fs::path& filename, const uint64_t& start_idx, uint64_t& n_txes, const std::function<bool(bool, uint64_t, const cryptonote::transaction_prefix&)>& f)
{
MDB_env *env;
MDB_dbi dbi_blocks, dbi_txs;
MDB_txn *txn;
MDB_cursor *cur_blocks, *cur_txs;
int dbr;
bool tx_active = false;
MDB_val k;
MDB_val v;
dbr = mdb_env_create(&env);
if (dbr) throw std::runtime_error("Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env, 3);
if (dbr) throw std::runtime_error("Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_open(env, filename.string().c_str(), 0, 0664);
if (dbr) throw std::runtime_error("Failed to open rings database file '"
+ filename.u8string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
dbr = mdb_dbi_open(txn, "blocks", MDB_INTEGERKEY, &dbi_blocks);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(txn, "txs_pruned", MDB_INTEGERKEY, &dbi_txs);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn, dbi_blocks, &cur_blocks);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn, dbi_txs, &cur_txs);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
MDB_stat stat;
dbr = mdb_stat(txn, dbi_blocks, &stat);
if (dbr) throw std::runtime_error("Failed to query txs stat: " + std::string(mdb_strerror(dbr)));
uint64_t n_blocks = stat.ms_entries;
dbr = mdb_stat(txn, dbi_txs, &stat);
if (dbr) throw std::runtime_error("Failed to query txs stat: " + std::string(mdb_strerror(dbr)));
n_txes = stat.ms_entries;
bool fret = true;
MDB_cursor_op op_blocks = MDB_FIRST;
MDB_cursor_op op_txs = MDB_FIRST;
uint64_t tx_idx = 0;
while (1)
{
int ret = mdb_cursor_get(cur_blocks, &k, &v, op_blocks);
op_blocks = MDB_NEXT;
if (ret == MDB_NOTFOUND)
break;
if (ret)
throw std::runtime_error("Failed to enumerate blocks: " + std::string(mdb_strerror(ret)));
if (k.mv_size != sizeof(uint64_t))
throw std::runtime_error("Bad key size");
uint64_t height = *(const uint64_t*)k.mv_data;
2022-05-20 23:29:48 +02:00
std::string bd;
bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size);
block b;
if (!parse_and_validate_block_from_blob(bd, b))
throw std::runtime_error("Failed to parse block from blob retrieved from the db");
ret = mdb_cursor_get(cur_txs, &k, &v, op_txs);
if (ret)
2020-10-23 22:32:28 +02:00
throw std::runtime_error("Failed to fetch transaction " + tools::type_to_hex(get_transaction_hash(b.miner_tx)) + ": " + std::string(mdb_strerror(ret)));
op_txs = MDB_NEXT;
bool last_block = height == n_blocks - 1;
if (start_idx <= tx_idx++ && !f(last_block && b.tx_hashes.empty(), height, b.miner_tx))
{
fret = false;
break;
}
for (size_t i = 0; i < b.tx_hashes.size(); ++i)
{
const crypto::hash& txid = b.tx_hashes[i];
ret = mdb_cursor_get(cur_txs, &k, &v, op_txs);
if (ret)
2020-10-23 22:32:28 +02:00
throw std::runtime_error("Failed to fetch transaction " + tools::type_to_hex(txid) + ": " + std::string(mdb_strerror(ret)));
if (start_idx <= tx_idx++)
{
cryptonote::transaction_prefix tx;
bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size);
CHECK_AND_ASSERT_MES(parse_and_validate_tx_prefix_from_blob(bd, tx), false, "Failed to parse transaction from blob");
if (!f(last_block && i == b.tx_hashes.size() - 1, height, tx))
{
fret = false;
break;
}
}
}
if (!fret)
break;
}
mdb_cursor_close(cur_blocks);
mdb_cursor_close(cur_txs);
mdb_txn_commit(txn);
tx_active = false;
mdb_dbi_close(env, dbi_blocks);
mdb_dbi_close(env, dbi_txs);
mdb_env_close(env);
return fret;
}
static uint64_t find_first_diverging_transaction(const fs::path& first_filename, const fs::path& second_filename)
{
MDB_env *env[2];
MDB_dbi dbi[2];
MDB_txn *txn[2];
MDB_cursor *cur[2];
int dbr;
bool tx_active[2] = { false, false };
uint64_t n_txes[2];
MDB_val k;
MDB_val v[2];
2021-01-04 04:19:42 +01:00
OXEN_DEFER {
RPC overhaul High-level details: This redesigns the RPC layer to make it much easier to work with, decouples it from an embedded HTTP server, and gets the vast majority of the RPC serialization and dispatch code out of a very commonly included header. There is unfortunately rather a lot of interconnected code here that cannot be easily separated out into separate commits. The full details of what happens here are as follows: Major details: - All of the RPC code is now in a `cryptonote::rpc` namespace; this renames quite a bit to be less verbose: e.g. CORE_RPC_STATUS_OK becomes `rpc::STATUS_OK`, and `cryptonote::COMMAND_RPC_SOME_LONG_NAME` becomes `rpc::SOME_LONG_NAME` (or just SOME_LONG_NAME for code already working in the `rpc` namespace). - `core_rpc_server` is now completely decoupled from providing any request protocol: it is now *just* the core RPC call handler. - The HTTP RPC interface now lives in a new rpc/http_server.h; this code handles listening for HTTP requests and dispatching them to core_rpc_server, then sending the results back to the caller. - There is similarly a rpc/lmq_server.h for LMQ RPC code; more details on this (and other LMQ specifics) below. - RPC implementing code now returns the response object and throws when things go wrong which simplifies much of the rpc error handling. They can throw anything; generic exceptions get logged and a generic "internal error" message gets returned to the caller, but there is also an `rpc_error` class to return an error code and message used by some json-rpc commands. - RPC implementing functions now overload `core_rpc_server::invoke` following the pattern: RPC_BLAH_BLAH::response core_rpc_server::invoke(RPC_BLAH_BLAH::request&& req, rpc_context context); This overloading makes the code vastly simpler: all instantiations are now done with a small amount of generic instantiation code in a single .cpp rather than needing to go to hell and back with a nest of epee macros in a core header. - each RPC endpoint is now defined by the RPC types themselves, including its accessible names and permissions, in core_rpc_server_commands_defs.h: - every RPC structure now has a static `names()` function that returns the names by which the end point is accessible. (The first one is the primary, the others are for deprecated aliases). - RPC command wrappers define their permissions and type by inheriting from special tag classes: - rpc::RPC_COMMAND is a basic, admin-only, JSON command, available via JSON RPC. *All* JSON commands are now available via JSON RPC, instead of the previous mix of some being at /foo and others at /json_rpc. (Ones that were previously at /foo are still there for backwards compatibility; see `rpc::LEGACY` below). - rpc::PUBLIC specifies that the command should be available via a restricted RPC connection. - rpc::BINARY specifies that the command is not JSON, but rather is accessible as /name and takes and returns values in the magic epee binary "portable storage" (lol) data format. - rpc::LEGACY specifies that the command should be available via the non-json-rpc interface at `/name` for backwards compatibility (in addition to the JSON-RPC interface). - some epee serialization got unwrapped and de-templatized so that it can be moved into a .cpp file with just declarations in the .h. (This makes a *huge* difference for core_rpc_server_commands_defs.h and for every compilation unit that includes it which previously had to compile all the serialization code and then throw all by one copy away at link time). This required some new macros so as to not break a ton of places that will use the old way putting everything in the headers; The RPC code uses this as does a few other places; there are comments in contrib/epee/include/serialization/keyvalue_serialization.h as to how to use it. - Detemplatized a bunch of epee/storages code. Most of it should have have been using templates at all (because it can only ever be called with one type!), and now it isn't. This broke some things that didn't properly compile because of missing headers or (in one case) a messed up circular dependency. - Significantly simplified a bunch of over-templatized serialization code. - All RPC serialization definitions is now out of core_rpc_server_commands_defs.h and into a single .cpp file (core_rpc_server_commands_defs.cpp). - core RPC no longer uses the disgusting BEGIN_URI_MAP2/MAP_URI_BLAH_BLAH macros. This was a terrible design that forced slamming tons of code into a common header that didn't need to be there. - epee::struct_init is gone. It was a horrible hack that instiated multiple templates just so the coder could be so lazy and write `some_type var;` instead of properly value initializing with `some_type var{};`. - Removed a bunch of useless crap from epee. In particular, forcing extra template instantiations all over the place in order to nest return objects inside JSON RPC values is no longer needed, as are a bunch of stuff related to the above de-macroization of the code. - get_all_service_nodes, get_service_nodes, and get_n_service_nodes are now combined into a single `get_service_nodes` (with deprecated aliases for the others), which eliminates a fair amount of duplication. The biggest obstacle here was getting the requested fields reference passed through: this is now done by a new ability to stash a context in the serialization object that can be retrieved by a sub-serialized type. LMQ-specifics: - The LokiMQ instance moves into `cryptonote::core` rather than being inside cryptonote_protocol. Currently the instance is used both for qnet and rpc calls (and so needs to be in a common place), but I also intend future PRs to use the batching code for job processing (replacing the current threaded job queue). - rpc/lmq_server.h handles the actual LMQ-request-to-core-RPC glue. Unlike http_server it isn't technically running the whole LMQ stack from here, but the parallel name with http_server seemed appropriate. - All RPC endpoints are supported by LMQ under the same names as defined generically, but prefixed with `rpc.` for public commands and `admin.` for restricted ones. - service node keys are now always available, even when not running in `--service-node` mode: this is because we want the x25519 key for being able to offer CURVE encryption for lmq RPC end-points, and because it doesn't hurt to have them available all the time. In the RPC layer this is now called "get_service_keys" (with "get_service_node_key" as an alias) since they aren't strictly only for service nodes. This also means code needs to check m_service_node, and not m_service_node_keys, to tell if it is running as a service node. (This is also easier to notice because m_service_node_keys got renamed to `m_service_keys`). - Added block and mempool monitoring LMQ RPC endpoints: `sub.block` and `sub.mempool` subscribes the connection for new block and new mempool TX notifications. The latter can notify on just blink txes, or all new mempool txes (but only new ones -- txes dumped from a block don't trigger it). The client gets pushed a [`notify.block`, `height`, `hash`] or [`notify.tx`, `txhash`, `blob`] message when something arrives. Minor details: - rpc::version_t is now a {major,minor} pair. Forcing everyone to pack and unpack a uint32_t was gross. - Changed some macros to constexprs (e.g. CORE_RPC_ERROR_CODE_...). (This immediately revealed a couple of bugs in the RPC code that was assigning CORE_RPC_ERROR_CODE_... to a string, and it worked because the macro allows implicit conversion to a char). - De-templatizing useless templates in epee (i.e. a bunch of templated types that were never invoked with different types) revealed a painful circular dependency between epee and non-epee code for tor_address and i2p_address. This crap is now handled in a suitably named `net/epee_network_address_hack.cpp` hack because it really isn't trivial to extricate this mess. - Removed `epee/include/serialization/serialize_base.h`. Amazingly the code somehow still all works perfectly with this previously vital header removed. - Removed bitrotted, unused epee "crypted_storage" and "gzipped_inmemstorage" code. - Replaced a bunch of epee::misc_utils::auto_scope_leave_caller with LOKI_DEFERs. The epee version involves quite a bit more instantiation and is ugly as sin. Also made the `loki::defer` class invokable for some edge cases that need calling before destruction in particular conditions. - Moved the systemd code around; it makes much more sense to do the systemd started notification as in daemon.cpp as late as possible rather than in core (when we can still have startup failures, e.g. if the RPC layer can't start). - Made the systemd short status string available in the get_info RPC (and no longer require building with systemd). - during startup, print (only) the x25519 when not in SN mode, and continue to print all three when in SN mode. - DRYed out some RPC implementation code (such as set_limit) - Made wallet_rpc stop using a raw m_wallet pointer
2020-04-28 01:25:43 +02:00
for (int i = 0; i < 2; i++)
if (tx_active[i]) mdb_txn_abort(txn[i]);
};
for (int i = 0; i < 2; ++i)
{
dbr = mdb_env_create(&env[i]);
if (dbr) throw std::runtime_error("Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env[i], 2);
if (dbr) throw std::runtime_error("Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
const fs::path& actual_filename = i ? second_filename : first_filename;
dbr = mdb_env_open(env[i], actual_filename.string().c_str(), 0, 0664);
if (dbr) throw std::runtime_error("Failed to open rings database file '"
+ actual_filename.u8string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env[i], NULL, MDB_RDONLY, &txn[i]);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
tx_active[i] = true;
dbr = mdb_dbi_open(txn[i], "txs_pruned", MDB_INTEGERKEY, &dbi[i]);
if (dbr)
dbr = mdb_dbi_open(txn[i], "txs", MDB_INTEGERKEY, &dbi[i]);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn[i], dbi[i], &cur[i]);
if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
MDB_stat stat;
dbr = mdb_stat(txn[i], dbi[i], &stat);
if (dbr) throw std::runtime_error("Failed to query m_block_info: " + std::string(mdb_strerror(dbr)));
n_txes[i] = stat.ms_entries;
}
if (n_txes[0] == 0 || n_txes[1] == 0)
throw std::runtime_error("No transaction in the database");
uint64_t lo = 0, hi = std::min(n_txes[0], n_txes[1]) - 1;
while (lo <= hi)
{
uint64_t mid = (lo + hi) / 2;
k.mv_size = sizeof(uint64_t);
k.mv_data = (void*)&mid;
dbr = mdb_cursor_get(cur[0], &k, &v[0], MDB_SET);
if (dbr) throw std::runtime_error("Failed to query transaction: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_get(cur[1], &k, &v[1], MDB_SET);
if (dbr) throw std::runtime_error("Failed to query transaction: " + std::string(mdb_strerror(dbr)));
if (v[0].mv_size == v[1].mv_size && !memcmp(v[0].mv_data, v[1].mv_data, v[0].mv_size))
lo = mid + 1;
else
hi = mid - 1;
}
for (int i = 0; i < 2; ++i)
{
mdb_cursor_close(cur[i]);
Merge pull request #4459 bcf3f6af fuzz_tests: catch unhandled exceptions (moneromooo-monero) 3ebd05d4 miner: restore stream flags after changing them (moneromooo-monero) a093092e levin_protocol_handler_async: do not propagate exception through dtor (moneromooo-monero) 1eebb82b net_helper: do not propagate exceptions through dtor (moneromooo-monero) fb6a3630 miner: do not propagate exceptions through dtor (moneromooo-monero) 2e2139ff epee: do not propagate exception through dtor (moneromooo-monero) 0749a8bd db_lmdb: do not propagate exceptions in dtor (moneromooo-monero) 1b0afeeb wallet_rpc_server: exit cleanly on unhandled exceptions (moneromooo-monero) 418a9936 unit_tests: catch unhandled exceptions (moneromooo-monero) ea7f9543 threadpool: do not propagate exceptions through the dtor (moneromooo-monero) 6e855422 gen_multisig: nice exit on unhandled exception (moneromooo-monero) 53df2deb db_lmdb: catch error in mdb_stat calls during migration (moneromooo-monero) e67016dd blockchain_blackball: catch failure to commit db transaction (moneromooo-monero) 661439f4 mlog: don't remove old logs if we failed to rename the current file (moneromooo-monero) 5fdcda50 easylogging++: test for NULL before dereference (moneromooo-monero) 7ece1550 performance_test: fix bad last argument calling add_arg (moneromooo-monero) a085da32 unit_tests: add check for page size > 0 before dividing (moneromooo-monero) d8b1ec8b unit_tests: use std::shared_ptr to shut coverity up about leaks (moneromooo-monero) 02563bf4 simplewallet: top level exception catcher to print nicer messages (moneromooo-monero) c57a65b2 blockchain_blackball: fix shift range for 32 bit archs (moneromooo-monero)
2018-09-29 22:17:00 +02:00
dbr = mdb_txn_commit(txn[i]);
if (dbr) throw std::runtime_error("Failed to query transaction: " + std::string(mdb_strerror(dbr)));
tx_active[i] = false;
mdb_dbi_close(env[i], dbi[i]);
mdb_env_close(env[i]);
}
return hi;
}
static std::vector<uint64_t> canonicalize(const std::vector<uint64_t> &v)
{
std::vector<uint64_t> c;
c.reserve(v.size());
c.push_back(v[0]);
for (size_t n = 1; n < v.size(); ++n)
{
if (v[n] != 0)
c.push_back(v[n]);
}
if (c.size() < v.size())
{
log::info(logcat, "Ring has duplicate member(s): {}", tools::join(" ", v));
}
return c;
}
static uint64_t get_num_spent_outputs()
{
MDB_txn *txn;
bool tx_active = false;
int dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
MDB_cursor *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)));
MDB_val k, v;
mdb_size_t count = 0, tmp;
MDB_cursor_op op = MDB_FIRST;
while (1)
{
dbr = mdb_cursor_get(cur, &k, &v, op);
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)));
count += tmp;
}
mdb_cursor_close(cur);
dbr = mdb_txn_commit(txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to commit txn: " + std::string(mdb_strerror(dbr)));
tx_active = false;
return count;
}
static bool add_spent_output(MDB_cursor *cur, const output_data &od)
{
MDB_val k = {sizeof(od.amount), (void*)&od.amount};
MDB_val v = {sizeof(od.offset), (void*)&od.offset};
int dbr = mdb_cursor_put(cur, &k, &v, MDB_NODUPDATA);
if (dbr == MDB_KEYEXIST)
return false;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to add spent output: " + std::string(mdb_strerror(dbr)));
return true;
}
static bool is_output_spent(MDB_cursor *cur, const output_data &od)
{
MDB_val k = {sizeof(od.amount), (void*)&od.amount};
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)));
bool spent = dbr == 0;
return spent;
}
static std::vector<output_data> get_spent_outputs(MDB_txn *txn)
{
MDB_cursor *cur;
int 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)));
MDB_val k, v;
mdb_size_t count = 0;
dbr = mdb_cursor_get(cur, &k, &v, MDB_FIRST);
if (dbr != MDB_NOTFOUND)
{
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get first spent output: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_count(cur, &count);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to count entries: " + std::string(mdb_strerror(dbr)));
}
std::vector<output_data> outs;
outs.reserve(count);
while (1)
{
outs.push_back({*(const uint64_t*)k.mv_data, *(const uint64_t*)v.mv_data});
dbr = mdb_cursor_get(cur, &k, &v, MDB_NEXT);
if (dbr == MDB_NOTFOUND)
break;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get next spent output: " + std::string(mdb_strerror(dbr)));
}
mdb_cursor_close(cur);
return outs;
}
static void get_per_amount_outputs(MDB_txn *txn, uint64_t amount, uint64_t &total, uint64_t &spent)
{
MDB_cursor *cur;
int dbr = mdb_cursor_open(txn, dbi_per_amount, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for per amount outputs: " + std::string(mdb_strerror(dbr)));
MDB_val k, v;
mdb_size_t count = 0;
k.mv_size = sizeof(uint64_t);
k.mv_data = (void*)&amount;
dbr = mdb_cursor_get(cur, &k, &v, MDB_SET);
if (dbr == MDB_NOTFOUND)
{
total = spent = 0;
}
else
{
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get per amount outputs: " + std::string(mdb_strerror(dbr)));
total = ((const uint64_t*)v.mv_data)[0];
spent = ((const uint64_t*)v.mv_data)[1];
}
mdb_cursor_close(cur);
}
static void inc_per_amount_outputs(MDB_txn *txn, uint64_t amount, uint64_t total, uint64_t spent)
{
MDB_cursor *cur;
int dbr = mdb_cursor_open(txn, dbi_per_amount, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for per amount outputs: " + std::string(mdb_strerror(dbr)));
MDB_val k, v;
mdb_size_t count = 0;
k.mv_size = sizeof(uint64_t);
k.mv_data = (void*)&amount;
dbr = mdb_cursor_get(cur, &k, &v, MDB_SET);
if (dbr == 0)
{
total += ((const uint64_t*)v.mv_data)[0];
spent += ((const uint64_t*)v.mv_data)[1];
}
else
{
CHECK_AND_ASSERT_THROW_MES(dbr == MDB_NOTFOUND, "Failed to get per amount outputs: " + std::string(mdb_strerror(dbr)));
}
uint64_t data[2] = {total, spent};
v.mv_size = 2 * sizeof(uint64_t);
v.mv_data = (void*)data;
dbr = mdb_cursor_put(cur, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to write record for per amount outputs: " + std::string(mdb_strerror(dbr)));
mdb_cursor_close(cur);
}
static uint64_t get_processed_txidx(const std::string &name)
{
MDB_txn *txn;
bool tx_active = false;
int dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
uint64_t height = 0;
MDB_val k, v;
k.mv_data = (void*)name.c_str();
k.mv_size = name.size();
dbr = mdb_get(txn, dbi_processed_txidx, &k, &v);
if (dbr != MDB_NOTFOUND)
{
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get processed height: " + std::string(mdb_strerror(dbr)));
height = *(const uint64_t*)v.mv_data;
}
dbr = mdb_txn_commit(txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to commit txn: " + std::string(mdb_strerror(dbr)));
tx_active = false;
return height;
}
static void set_processed_txidx(MDB_txn *txn, const std::string &name, uint64_t height)
{
MDB_val k, v;
k.mv_data = (void*)name.c_str();
k.mv_size = name.size();
v.mv_data = (void*)&height;
v.mv_size = sizeof(height);
int dbr = mdb_put(txn, dbi_processed_txidx, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set processed height: " + std::string(mdb_strerror(dbr)));
}
static bool get_relative_ring(MDB_txn *txn, const crypto::key_image &ki, std::vector<uint64_t> &ring)
{
MDB_val k, v;
k.mv_data = (void*)&ki;
k.mv_size = sizeof(ki);
int dbr = mdb_get(txn, dbi_relative_rings, &k, &v);
if (dbr == MDB_NOTFOUND)
return false;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get relative ring: " + std::string(mdb_strerror(dbr)));
ring = decompress_ring(std::string((const char*)v.mv_data, v.mv_size));
return true;
}
static void set_relative_ring(MDB_txn *txn, const crypto::key_image &ki, const std::vector<uint64_t> &ring)
{
const std::string sring = compress_ring(ring);
MDB_val k, v;
k.mv_data = (void*)&ki;
k.mv_size = sizeof(ki);
v.mv_data = (void*)sring.c_str();
v.mv_size = sring.size();
int dbr = mdb_put(txn, dbi_relative_rings, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set relative ring: " + std::string(mdb_strerror(dbr)));
}
static std::string keep_under_511(const std::string &s)
{
if (s.size() <= 511)
return s;
crypto::hash hash;
crypto::cn_fast_hash(s.data(), s.size(), hash);
return std::string((const char*)&hash, 32);
}
static uint64_t get_ring_instances(MDB_txn *txn, uint64_t amount, const std::vector<uint64_t> &ring)
{
const std::string sring = keep_under_511(compress_ring(amount, ring));
MDB_val k, v;
k.mv_data = (void*)sring.data();
k.mv_size = sring.size();
int dbr = mdb_get(txn, dbi_ring_instances, &k, &v);
if (dbr == MDB_NOTFOUND)
return 0;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get ring instances: " + std::string(mdb_strerror(dbr)));
return *(const uint64_t*)v.mv_data;
}
static uint64_t get_ring_subset_instances(MDB_txn *txn, uint64_t amount, const std::vector<uint64_t> &ring)
{
uint64_t instances = get_ring_instances(txn, amount, ring);
if (ring.size() > 11)
return instances;
uint64_t extra = 0;
std::vector<uint64_t> subset;
subset.reserve(ring.size());
Merge pull request #4459 bcf3f6af fuzz_tests: catch unhandled exceptions (moneromooo-monero) 3ebd05d4 miner: restore stream flags after changing them (moneromooo-monero) a093092e levin_protocol_handler_async: do not propagate exception through dtor (moneromooo-monero) 1eebb82b net_helper: do not propagate exceptions through dtor (moneromooo-monero) fb6a3630 miner: do not propagate exceptions through dtor (moneromooo-monero) 2e2139ff epee: do not propagate exception through dtor (moneromooo-monero) 0749a8bd db_lmdb: do not propagate exceptions in dtor (moneromooo-monero) 1b0afeeb wallet_rpc_server: exit cleanly on unhandled exceptions (moneromooo-monero) 418a9936 unit_tests: catch unhandled exceptions (moneromooo-monero) ea7f9543 threadpool: do not propagate exceptions through the dtor (moneromooo-monero) 6e855422 gen_multisig: nice exit on unhandled exception (moneromooo-monero) 53df2deb db_lmdb: catch error in mdb_stat calls during migration (moneromooo-monero) e67016dd blockchain_blackball: catch failure to commit db transaction (moneromooo-monero) 661439f4 mlog: don't remove old logs if we failed to rename the current file (moneromooo-monero) 5fdcda50 easylogging++: test for NULL before dereference (moneromooo-monero) 7ece1550 performance_test: fix bad last argument calling add_arg (moneromooo-monero) a085da32 unit_tests: add check for page size > 0 before dividing (moneromooo-monero) d8b1ec8b unit_tests: use std::shared_ptr to shut coverity up about leaks (moneromooo-monero) 02563bf4 simplewallet: top level exception catcher to print nicer messages (moneromooo-monero) c57a65b2 blockchain_blackball: fix shift range for 32 bit archs (moneromooo-monero)
2018-09-29 22:17:00 +02:00
for (uint64_t mask = 1; mask < (((uint64_t)1) << ring.size()) - 1; ++mask)
{
subset.resize(0);
for (size_t i = 0; i < ring.size(); ++i)
if ((mask >> i) & 1)
subset.push_back(ring[i]);
extra += get_ring_instances(txn, amount, subset);
}
return instances + extra;
}
static uint64_t inc_ring_instances(MDB_txn *txn, uint64_t amount, const std::vector<uint64_t> &ring)
{
const std::string sring = keep_under_511(compress_ring(amount, ring));
MDB_val k, v;
k.mv_data = (void*)sring.data();
k.mv_size = sring.size();
int dbr = mdb_get(txn, dbi_ring_instances, &k, &v);
CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_NOTFOUND, "Failed to get ring instances: " + std::string(mdb_strerror(dbr)));
uint64_t count;
if (dbr == MDB_NOTFOUND)
count = 1;
else
count = 1 + *(const uint64_t*)v.mv_data;
v.mv_data = &count;
v.mv_size = sizeof(count);
dbr = mdb_put(txn, dbi_ring_instances, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set ring instances: " + std::string(mdb_strerror(dbr)));
return count;
}
static std::vector<crypto::key_image> get_key_images(MDB_txn *txn, const output_data &od)
{
MDB_val k, v;
k.mv_data = (void*)&od;
k.mv_size = sizeof(od);
int dbr = mdb_get(txn, dbi_outputs, &k, &v);
CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_NOTFOUND, "Failed to get output: " + std::string(mdb_strerror(dbr)));
if (dbr == MDB_NOTFOUND)
return {};
CHECK_AND_ASSERT_THROW_MES(v.mv_size % 32 == 0, "Unexpected record size");
std::vector<crypto::key_image> key_images;
key_images.reserve(v.mv_size / 32);
const crypto::key_image *ki = (const crypto::key_image*)v.mv_data;
for (size_t n = 0; n < v.mv_size / 32; ++n)
key_images.push_back(*ki++);
return key_images;
}
static void add_key_image(MDB_txn *txn, const output_data &od, const crypto::key_image &ki)
{
MDB_val k, v;
k.mv_data = (void*)&od;
k.mv_size = sizeof(od);
int dbr = mdb_get(txn, dbi_outputs, &k, &v);
CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_NOTFOUND, "Failed to get output");
std::string data;
if (!dbr)
{
CHECK_AND_ASSERT_THROW_MES(v.mv_size % 32 == 0, "Unexpected record size");
data = std::string((const char*)v.mv_data, v.mv_size);
}
data += std::string((const char*)&ki, sizeof(ki));
v.mv_data = (void*)data.data();
v.mv_size = data.size();
dbr = mdb_put(txn, dbi_outputs, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set outputs: " + std::string(mdb_strerror(dbr)));
}
2018-08-12 11:47:03 +02:00
static bool get_stat(MDB_txn *txn, const char *key, uint64_t &data)
{
MDB_val k, v;
k.mv_data = (void*)key;
k.mv_size = strlen(key);
int dbr = mdb_get(txn, dbi_stats, &k, &v);
if (dbr == MDB_NOTFOUND)
return false;
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to get stat record");
CHECK_AND_ASSERT_THROW_MES(v.mv_size == sizeof(uint64_t), "Unexpected record size");
data = *(const uint64_t*)v.mv_data;
return true;
}
static void set_stat(MDB_txn *txn, const char *key, uint64_t data)
{
MDB_val k, v;
k.mv_data = (void*)key;
k.mv_size = strlen(key);
v.mv_data = (void*)&data;
v.mv_size = sizeof(uint64_t);
int dbr = mdb_put(txn, dbi_stats, &k, &v, 0);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set stat record");
}
static void inc_stat(MDB_txn *txn, const char *key)
{
uint64_t data;
if (!get_stat(txn, key, data))
data = 0;
++data;
set_stat(txn, key, data);
}
static void open_db(const fs::path& filename, MDB_env** env, MDB_txn** txn, MDB_cursor** cur, MDB_dbi* dbi)
{
std::error_code ec;
if (fs::create_directories(filename, ec); ec)
log::warning(logcat, "Failed to create lmdb path {}: {}", filename, ec.message());
int flags = MDB_RDONLY;
if (db_flags & DBF_FAST)
flags |= MDB_NOSYNC;
if (db_flags & DBF_FASTEST)
flags |= MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC;
int dbr = mdb_env_create(env);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(*env, 1);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
log::info(logcat, "Opening oxen blockchain at {}", filename);
dbr = mdb_env_open(*env, filename.string().c_str(), flags, 0664);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open rings database file '"
+ filename.u8string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(*env, NULL, MDB_RDONLY, txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
dbr = mdb_dbi_open(*txn, "output_amounts", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, dbi);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
mdb_set_dupsort(*txn, *dbi, compare_uint64);
dbr = mdb_cursor_open(*txn, *dbi, cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr)));
}
static void close_db(MDB_env *env, MDB_txn *txn, MDB_cursor *cur, MDB_dbi dbi)
{
mdb_txn_abort(txn);
mdb_cursor_close(cur);
mdb_dbi_close(env, dbi);
mdb_env_close(env);
}
2018-08-12 11:47:03 +02:00
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;
MDB_val k = { sizeof(amount), (void*)&amount }, v;
int dbr = mdb_cursor_get(cur, &k, &v, MDB_SET);
if (dbr == MDB_NOTFOUND)
{
rct = 0;
}
else
{
if (dbr) throw std::runtime_error("Record 0 not found: " + std::string(mdb_strerror(dbr)));
mdb_size_t count = 0;
dbr = mdb_cursor_count(cur, &count);
if (dbr) throw std::runtime_error("Failed to count records: " + std::string(mdb_strerror(dbr)));
rct = count;
}
MDB_stat s;
dbr = mdb_stat(txn, dbi, &s);
if (dbr) throw std::runtime_error("Failed to count records: " + std::string(mdb_strerror(dbr)));
if (s.ms_entries < rct) throw std::runtime_error("Inconsistent records: " + std::string(mdb_strerror(dbr)));
pre_rct = s.ms_entries - rct;
}
static crypto::hash get_genesis_block_hash(const fs::path& filename)
{
MDB_env *env;
MDB_dbi dbi;
MDB_txn *txn;
int dbr;
bool tx_active = false;
dbr = mdb_env_create(&env);
if (dbr) throw std::runtime_error("Failed to create LDMB environment: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_set_maxdbs(env, 1);
if (dbr) throw std::runtime_error("Failed to set max env dbs: " + std::string(mdb_strerror(dbr)));
dbr = mdb_env_open(env, filename.string().c_str(), 0, 0664);
if (dbr) throw std::runtime_error("Failed to open rings database file '"
+ filename.u8string() + "': " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
2021-01-04 04:19:42 +01:00
OXEN_DEFER { if (tx_active) mdb_txn_abort(txn); };
tx_active = true;
dbr = mdb_dbi_open(txn, "block_info", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi);
mdb_set_dupsort(txn, dbi, compare_uint64);
if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr)));
uint64_t zero = 0;
MDB_val k = { sizeof(uint64_t), (void*)&zero}, v;
dbr = mdb_get(txn, dbi, &k, &v);
if (dbr) throw std::runtime_error("Failed to retrieve genesis block: " + std::string(mdb_strerror(dbr)));
crypto::hash genesis_block_hash = *(const crypto::hash*)(((const uint64_t*)v.mv_data) + 5);
mdb_dbi_close(env, dbi);
mdb_txn_abort(txn);
mdb_env_close(env);
tx_active = false;
return genesis_block_hash;
}
static std::vector<std::pair<uint64_t, uint64_t>> load_outputs(const fs::path& filename)
{
std::vector<std::pair<uint64_t, uint64_t>> outputs;
uint64_t amount = std::numeric_limits<uint64_t>::max();
FILE* f =
#ifdef _WIN32
_wfopen(filename.c_str(), L"r");
#else
fopen(filename.c_str(), "r");
#endif
if (!f)
{
log::error(logcat, "Failed to load outputs from {}: {}", filename, strerror(errno));
return {};
}
while (1)
{
char s[256];
if (!fgets(s, sizeof(s), f))
{
log::error(logcat, "Error reading from {}: {}", filename, strerror(errno));
break;
}
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())
{
log::error(logcat, "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
{
log::error(logcat, "Bad format in {}", filename);
continue;
}
}
fclose(f);
return outputs;
}
static bool export_spent_outputs(MDB_cursor* cur, const fs::path& filename)
{
FILE* f =
#ifdef _WIN32
_wfopen(filename.c_str(), L"w");
#else
fopen(filename.c_str(), "w");
#endif
if (!f)
{
log::error(logcat, "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);
log::error(logcat, "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 "*%zu\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 "*%zu\n", pending_offsets.front(), pending_offsets.size());
pending_offsets.clear();
}
fclose(f);
return true;
}
int main(int argc, char* argv[])
{
TRY_ENTRY();
epee::string_tools::set_module_name_and_folder(argv[0]);
tools::on_startup();
auto opt_size = command_line::boost_option_sizes();
po::options_description desc_cmd_only("Command line options", opt_size.first, opt_size.second);
po::options_description desc_cmd_sett("Command line options and settings options", opt_size.first, opt_size.second);
const command_line::arg_descriptor<std::string> arg_blackball_db_dir = {
"spent-output-db-dir", "Specify spent output database directory",
get_default_db_path().u8string(),
};
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<bool> arg_rct_only = {"rct-only", "Only work on ringCT outputs", false};
const command_line::arg_descriptor<bool> arg_check_subsets = {"check-subsets", "Check ring subsets (very expensive)", false};
const command_line::arg_descriptor<bool> arg_verbose = {"verbose", "Verbose output)", false};
2021-01-04 04:19:42 +01:00
const command_line::arg_descriptor<std::vector<std::string> > arg_inputs = {"inputs", "Path to Oxen DB, and path to any fork DBs"};
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
"db-sync-mode"
, "Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]."
, "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"};
const command_line::arg_descriptor<bool> arg_force_chain_reaction_pass = {"force-chain-reaction-pass", "Run the chain reaction pass even if no new blockchain data was processed"};
const command_line::arg_descriptor<bool> arg_historical_stat = {"historical-stat", "Report historical stat of spent outputs for every 10000 blocks window"};
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_rct_only);
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_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_force_chain_reaction_pass);
command_line::add_arg(desc_cmd_sett, arg_historical_stat);
command_line::add_arg(desc_cmd_sett, arg_inputs);
command_line::add_arg(desc_cmd_only, command_line::arg_help);
po::options_description desc_options("Allowed options");
desc_options.add(desc_cmd_only).add(desc_cmd_sett);
po::positional_options_description positional_options;
positional_options.add(arg_inputs.name, -1);
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_options, [&]()
{
auto parser = po::command_line_parser(argc, argv).options(desc_options).positional(positional_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (! r)
return 1;
if (command_line::get_arg(vm, command_line::arg_help))
{
2021-01-04 04:19:42 +01:00
std::cout << "Oxen '" << OXEN_RELEASE_NAME << "' (v" << OXEN_VERSION_FULL << ")\n\n";
std::cout << desc_options << std::endl;
return 1;
}
auto m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir);
auto log_file_path = m_config_folder + "oxen-blockchain-mark-spent-outputs.log";
log::Level log_level;
if(auto level = oxen::logging::parse_level(command_line::get_arg(vm, arg_log_level).c_str())) {
log_level = *level;
} else {
std::cerr << "Incorrect log level: " << command_line::get_arg(vm, arg_log_level).c_str() << std::endl;
throw std::runtime_error{"Incorrect log level"};
}
oxen::logging::init(log_file_path, log_level);
log::warning(logcat, "Starting...");
fs::path output_file_path = fs::u8path(command_line::get_arg(vm, arg_blackball_db_dir));
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_verbose = command_line::get_arg(vm, arg_verbose);
bool opt_force_chain_reaction_pass = command_line::get_arg(vm, arg_force_chain_reaction_pass);
bool opt_historical_stat = command_line::get_arg(vm, arg_historical_stat);
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_sync_mode = command_line::get_arg(vm, arg_db_sync_mode);
if (!parse_db_sync_mode(db_sync_mode))
{
log::error(logcat, "Invalid db sync mode: {}", db_sync_mode);
return 1;
}
std::vector<fs::path> inputs;
for (auto& in : command_line::get_arg(vm, arg_inputs))
inputs.push_back(fs::u8path(in));
if (inputs.empty())
{
log::warning(logcat, "No inputs given");
return 1;
}
2018-06-29 06:47:00 +02:00
std::vector<Blockchain*> core_storage(inputs.size());
for (size_t n = 0; n < inputs.size(); ++n)
{
blockchain_objects_t *blockchain_objects = new blockchain_objects_t();
2018-06-29 06:47:00 +02:00
core_storage[n] = &(blockchain_objects->m_blockchain);
}
fs::path cache_dir = output_file_path / "spent-outputs-cache";
init(cache_dir);
log::warning(logcat, "Scanning for spent outputs...");
size_t done = 0;
const uint64_t start_blackballed_outputs = get_num_spent_outputs();
2020-10-23 22:32:28 +02:00
tools::ringdb ringdb(output_file_path.string(), tools::type_to_hex(get_genesis_block_hash(inputs[0])));
bool stop_requested = false;
tools::signal_handler::install([&stop_requested](int type) {
stop_requested = true;
});
int dbr = resize_env(cache_dir.string().c_str());
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to resize LMDB database: " + std::string(mdb_strerror(dbr)));
// open first db
MDB_env *env0;
MDB_txn *txn0;
MDB_dbi dbi0;
MDB_cursor *cur0;
open_db(inputs[0], &env0, &txn0, &cur0, &dbi0);
std::vector<output_data> work_spent;
if (opt_historical_stat)
{
if (!start_blackballed_outputs)
{
log::info(logcat, "Spent outputs database is empty. Either you haven't run the analysis mode yet, or there is really no output marked as spent.");
goto skip_secondary_passes;
}
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)));
const uint64_t STAT_WINDOW = 10000;
uint64_t outs_total = 0;
uint64_t outs_spent = 0;
std::unordered_map<uint64_t, uint64_t> outs_per_amount;
uint64_t start_idx = 0, n_txes;
uint64_t prev_height = 0;
for_all_transactions(inputs[0], start_idx, n_txes, [&](bool last_tx, uint64_t height, const cryptonote::transaction_prefix &tx)->bool
{
if (height != prev_height)
{
if (height % 100 == 0) std::cout << "\r" << height << ": " << (100.0f * outs_spent / outs_total) << "% ( " << outs_spent << " / " << outs_total << " ) \r" << std::flush;
if (height % STAT_WINDOW == 0)
{
uint64_t window_front = (height / STAT_WINDOW - 1) * STAT_WINDOW;
uint64_t window_back = window_front + STAT_WINDOW - 1;
log::warning(logcat, "{}-{}: {}% ( {} / {} )", window_front, window_back, (100.0f * outs_spent / outs_total), outs_spent, outs_total);
outs_total = outs_spent = 0;
}
}
prev_height = height;
for (const auto &out: tx.vout)
{
++outs_total;
2020-06-22 03:42:32 +02:00
CHECK_AND_ASSERT_THROW_MES(std::holds_alternative<txout_to_key>(out.target), "Out target type is not txout_to_key: height=" + std::to_string(height));
uint64_t out_global_index = outs_per_amount[out.amount]++;
if (is_output_spent(cur, output_data(out.amount, out_global_index)))
++outs_spent;
}
if (last_tx)
{
uint64_t window_front = (height / STAT_WINDOW) * STAT_WINDOW;
uint64_t window_back = height;
log::warning(logcat, "{}-{}: {}% ( {} / {} )", window_front, window_back, (100.0f * outs_spent / outs_total), outs_spent, outs_total);
}
if (stop_requested)
{
log::info(logcat, "Stopping scan...");
return false;
}
return true;
});
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)));
goto skip_secondary_passes;
}
if (!extra_spent_outputs.empty())
{
log::info(logcat, "Adding {} extra spent outputs", extra_spent_outputs.size());
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);
if (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)
{
const std::string canonical = fs::canonical(inputs[n]).u8string();
uint64_t start_idx = get_processed_txidx(canonical);
if (n > 0 && start_idx == 0)
{
start_idx = find_first_diverging_transaction(inputs[0], inputs[n]);
log::warning(logcat, "First diverging transaction at {}", start_idx);
}
log::warning(logcat, "Reading blockchain from {} from {}", inputs[n], start_idx);
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)));
size_t records = 0;
std::vector<std::pair<uint64_t, uint64_t>> blackballs;
uint64_t n_txes;
for_all_transactions(inputs[n], start_idx, n_txes, [&](const cryptonote::transaction_prefix &tx)->bool
{
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
for (const auto &in: tx.vin)
{
const auto* txinp = std::get_if<txin_to_key>(&in);
if (!txinp || (opt_rct_only && txinp->amount != 0))
continue;
auto& txin = *txinp;
const std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(txin.key_offsets);
if (n == 0)
for (uint64_t out: absolute)
add_key_image(txn, output_data(txin.amount, out), txin.k_image);
std::vector<uint64_t> relative_ring;
std::vector<uint64_t> new_ring = canonicalize(txin.key_offsets);
const uint32_t ring_size = txin.key_offsets.size();
const uint64_t instances = inc_ring_instances(txn, txin.amount, new_ring);
uint64_t pa_total = 0, pa_spent = 0;
if (!opt_rct_only)
get_per_amount_outputs(txn, txin.amount, pa_total, pa_spent);
if (n == 0 && ring_size == 1)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[0]);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to being used in a 1-ring", output.first, output.second);
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
if (add_spent_output(cur, output_data(txin.amount, absolute[0])))
inc_stat(txn, txin.amount ? "pre-rct-ring-size-1" : "rct-ring-size-1");
}
else if (n == 0 && instances == new_ring.size())
{
for (size_t o = 0; o < new_ring.size(); ++o)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to being used in {} identical {}-rings", output.first, output.second, new_ring.size(), new_ring.size());
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
2018-10-08 19:36:51 +02:00
if (add_spent_output(cur, output_data(txin.amount, absolute[o])))
inc_stat(txn, txin.amount ? "pre-rct-duplicate-rings" : "rct-duplicate-rings");
}
}
else if (n == 0 && !opt_rct_only && pa_spent + 1 == pa_total)
{
for (size_t o = 0; o < pa_total; ++o)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, o);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to as many outputs of that amount being spent as exist so far", output.first, output.second);
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
if (add_spent_output(cur, output_data(txin.amount, o)))
inc_stat(txn, txin.amount ? "pre-rct-full-count" : "rct-full-count");
}
}
else if (n == 0 && opt_check_subsets && get_ring_subset_instances(txn, txin.amount, new_ring) >= new_ring.size())
{
for (size_t o = 0; o < new_ring.size(); ++o)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to being used in {} subsets of {}-rings", output.first, output.second, new_ring.size(), new_ring.size());
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
if (add_spent_output(cur, output_data(txin.amount, absolute[o])))
inc_stat(txn, txin.amount ? "pre-rct-subset-rings" : "rct-subset-rings");
}
}
else if (n > 0 && get_relative_ring(txn, txin.k_image, relative_ring))
{
log::debug(logcat, "Key image {} already seen: rings {}, {}", txin.k_image, tools::join(" ", relative_ring), tools::join(" ", txin.key_offsets));
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
if (relative_ring != txin.key_offsets)
{
log::debug(logcat, "Rings are different");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
const std::vector<uint64_t> r0 = cryptonote::relative_output_offsets_to_absolute(relative_ring);
const std::vector<uint64_t> r1 = cryptonote::relative_output_offsets_to_absolute(txin.key_offsets);
std::vector<uint64_t> common;
for (uint64_t out: r0)
{
if (std::find(r1.begin(), r1.end(), out) != r1.end())
common.push_back(out);
}
if (common.empty())
{
log::error(logcat, "Rings for the same key image are disjoint");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
else if (common.size() == 1)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, common[0]);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to being used in rings with a single common element", output.first, output.second);
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
if (add_spent_output(cur, output_data(txin.amount, common[0])))
inc_stat(txn, txin.amount ? "pre-rct-key-image-attack" : "rct-key-image-attack");
}
else
{
log::debug(logcat, "The intersection has more than one element, it's still ok");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
for (const auto &out: r0)
if (std::find(common.begin(), common.end(), out) != common.end())
new_ring.push_back(out);
new_ring = cryptonote::absolute_output_offsets_to_relative(new_ring);
}
}
}
if (n == 0)
{
set_relative_ring(txn, txin.k_image, new_ring);
if (!opt_rct_only)
inc_per_amount_outputs(txn, txin.amount, 0, 1);
}
}
set_processed_txidx(txn, canonical, start_idx+1);
if (!opt_rct_only)
{
const bool miner_tx = tx.vin.size() == 1 && std::holds_alternative<txin_gen>(tx.vin[0]);
for (const auto &out: tx.vout)
{
uint64_t amount = out.amount;
Make tx type and version scoped enums This converts the transaction type and version to scoped enum, giving type safety and making the tx type assignment less error prone because there is no implicit conversion or comparison with raw integers that has to be worried about. This ends up converting any use of `cryptonote::transaction::type_xyz` to `cryptonote::transaction::txtype::xyz`. For version, names like `transaction::version_v4` become `cryptonote::txversion::v4_tx_types`. This also allows/includes various other simplifications related to or enabled by this change: - handle `is_deregister` dynamically in serialization code (setting `type::standard` or `type::deregister` rather than using a version-determined union) - `get_type()` is no longer needed with the above change: it is now much simpler to directly access `type` which will always have the correct value (even for v2 or v3 transaction types). And though there was an assertion on the enum value, `get_type()` was being used only sporadically: many places accessed `.type` directly. - the old unscoped enum didn't have a type but was assumed castable to/from `uint16_t`, which technically meant there was potential undefined behaviour when deserializing any type values >= 8. - tx type range checks weren't being done in all serialization paths; they are now. Because `get_type()` was not used everywhere (lots of places simply accessed `.type` directory) these might not have been caught. - `set_type()` is not needed; it was only being used in a single place (wallet2.cpp) and only for v4 txes, so the version protection code was never doing anything. - added a std::ostream << operator for the enum types so that they can be output with `<< tx_type <<` rather than needing to wrap it in `type_to_string(tx_type)` everywhere. For the versions, you get the annotated version string (e.g. 4_tx_types) rather than just the number 4.
2019-06-11 20:53:46 +02:00
if (miner_tx && tx.version >= cryptonote::txversion::v2_ringct)
amount = 0;
if (opt_rct_only && amount != 0)
continue;
if (!std::holds_alternative<txout_to_key>(out.target))
continue;
inc_per_amount_outputs(txn, amount, 1, 0);
}
}
++records;
if (records >= records_per_sync)
{
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)));
int dbr = resize_env(cache_dir.string().c_str());
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to resize LMDB database: " + std::string(mdb_strerror(dbr)));
dbr = mdb_txn_begin(env, NULL, 0, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_open(txn, dbi_spent, &cur);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open LMDB cursor: " + std::string(mdb_strerror(dbr)));
records = 0;
}
if (stop_requested)
{
log::info(logcat, "Stopping scan...");
return false;
}
return true;
});
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)));
log::warning(logcat, "blockchain from {} processed till tx idx {}", inputs[n], start_idx);
if (stop_requested)
break;
}
if (stop_requested)
goto skip_secondary_passes;
if (opt_force_chain_reaction_pass || get_num_spent_outputs() > start_blackballed_outputs)
{
MDB_txn *txn;
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
work_spent = get_spent_outputs(txn);
mdb_txn_abort(txn);
}
while (!work_spent.empty())
{
log::warning(logcat, "Secondary pass on {} spent outputs", work_spent.size());
int dbr = resize_env(cache_dir.string().c_str());
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to resize LMDB database: " + std::string(mdb_strerror(dbr)));
MDB_txn *txn;
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;
std::vector<output_data> scan_spent = std::move(work_spent);
work_spent.clear();
for (const output_data &od: scan_spent)
{
std::vector<crypto::key_image> key_images = get_key_images(txn, od);
for (const crypto::key_image &ki: key_images)
{
std::vector<uint64_t> relative_ring;
CHECK_AND_ASSERT_THROW_MES(get_relative_ring(txn, ki, relative_ring), "Relative ring not found");
std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(relative_ring);
size_t known = 0;
uint64_t last_unknown = 0;
for (uint64_t out: absolute)
{
output_data new_od(od.amount, out);
if (is_output_spent(cur, new_od))
++known;
else
last_unknown = out;
}
if (known == absolute.size() - 1)
{
const std::pair<uint64_t, uint64_t> output = std::make_pair(od.amount, last_unknown);
if (opt_verbose)
{
log::info(logcat, "Marking output {}/{} as spent, due to being used in a {}-ring where all other outputs are known to be spent", output.first, output.second, absolute.size());
}
blackballs.push_back(output);
if (add_spent_output(cur, output_data(od.amount, last_unknown)))
inc_stat(txn, od.amount ? "pre-rct-chain-reaction" : "rct-chain-reaction");
work_spent.push_back(output_data(od.amount, last_unknown));
}
}
if (stop_requested)
{
log::info(logcat, "Stopping secondary passes. Secondary passes are not incremental, they will re-run fully.");
return 0;
}
}
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)));
}
skip_secondary_passes:
uint64_t diff = get_num_spent_outputs() - start_blackballed_outputs;
log::warning(logcat, "{} new outputs marked as spent, {} total outputs marked as spent", std::to_string(diff), get_num_spent_outputs());
2018-08-12 11:47:03 +02:00
MDB_txn *txn;
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr)));
uint64_t pre_rct = 0, rct = 0;
get_num_outputs(txn0, cur0, dbi0, pre_rct, rct);
log::info(logcat, "Total pre-rct outputs: {}", pre_rct);
log::info(logcat, "Total rct outputs: {}", rct);
2018-08-12 11:47:03 +02:00
static const struct { const char *key; uint64_t base; } stat_keys[] = {
{ "pre-rct-ring-size-1", pre_rct }, { "rct-ring-size-1", rct },
{ "pre-rct-duplicate-rings", pre_rct }, { "rct-duplicate-rings", rct },
{ "pre-rct-subset-rings", pre_rct }, { "rct-subset-rings", rct },
{ "pre-rct-full-count", pre_rct }, { "rct-full-count", rct },
2018-08-12 11:47:03 +02:00
{ "pre-rct-key-image-attack", pre_rct }, { "rct-key-image-attack", rct },
{ "pre-rct-extra", pre_rct }, { "rct-ring-extra", rct },
2018-08-12 11:47:03 +02:00
{ "pre-rct-chain-reaction", pre_rct }, { "rct-chain-reaction", rct },
};
for (const auto &key: stat_keys)
{
uint64_t data;
if (!get_stat(txn, key.key, data))
data = 0;
float percent = key.base ? 100.0f * data / key.base : 0.0f;
log::info(logcat, "{}: {} ({}%)", key.key, data, percent);
2018-08-12 11:47:03 +02:00
}
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::warning(logcat, "Blockchain spent output data exported OK");
close_db(env0, txn0, cur0, dbi0);
close();
return 0;
CATCH_ENTRY("Error", 1);
}