From b35d7c6cf1e803b6cb3d2efd45accd1fd3b9b782 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Thu, 17 Oct 2024 03:25:05 -0500 Subject: [PATCH] blockchain_utilities: add tool to track running counts of locked outputs --- src/blockchain_db/blockchain_db.h | 2 +- src/blockchain_db/lmdb/db_lmdb.cpp | 4 +- src/blockchain_db/lmdb/db_lmdb.h | 2 +- src/blockchain_db/testdb.h | 2 +- src/blockchain_utilities/CMakeLists.txt | 69 ++- .../blockchain_find_locked.cpp | 469 ++++++++++++++++++ src/cryptonote_core/blockchain.cpp | 10 - src/cryptonote_core/blockchain.h | 19 - 8 files changed, 539 insertions(+), 38 deletions(-) create mode 100644 src/blockchain_utilities/blockchain_find_locked.cpp diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 3e953da30..31c8b9686 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1743,7 +1743,7 @@ public: * * @return false if the function returns false for any output, otherwise true */ - virtual bool for_all_outputs(std::function f) const = 0; + virtual bool for_all_outputs(std::function f) const = 0; virtual bool for_all_outputs(uint64_t amount, const std::function &f) const = 0; /** diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index d01119249..29b9c9560 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -3696,7 +3696,7 @@ bool BlockchainLMDB::for_all_transactions(std::function f) const +bool BlockchainLMDB::for_all_outputs(std::function f) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -3720,7 +3720,7 @@ bool BlockchainLMDB::for_all_outputs(std::functionoutput_id); - if (!f(amount, toi.first, ok->data.height, toi.second)) { + if (!f(amount, toi.first, ok->data.height, toi.second, ok->data.unlock_time)) { fret = false; break; } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 6eeb942dc..64086d875 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -306,7 +306,7 @@ public: virtual bool for_all_key_images(std::function) const; virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function) const; virtual bool for_all_transactions(std::function, bool pruned) const; - virtual bool for_all_outputs(std::function f) const; + virtual bool for_all_outputs(std::function f) const; virtual bool for_all_outputs(uint64_t amount, const std::function &f) const; virtual bool for_all_alt_blocks(std::function f, bool include_blob = false) const; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 308bdd4c2..b35b7b531 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -120,7 +120,7 @@ public: virtual bool for_all_key_images(std::function) const override { return true; } virtual bool for_blocks_range(const uint64_t&, const uint64_t&, std::function) const override { return true; } virtual bool for_all_transactions(std::function, bool pruned) const override { return true; } - virtual bool for_all_outputs(std::function f) const override { return true; } + virtual bool for_all_outputs(std::function f) const override { return true; } virtual bool for_all_outputs(uint64_t amount, const std::function &f) const override { return true; } virtual bool is_read_only() const override { return false; } virtual std::map> get_output_histogram(const std::vector &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const override { return std::map>(); } diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index a41cd1e53..b51c2a009 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -26,6 +26,8 @@ # 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. +################################################################################ + set(blockchain_import_sources blockchain_import.cpp bootstrap_file.cpp @@ -41,6 +43,8 @@ set(blockchain_import_private_headers monero_private_headers(blockchain_import ${blockchain_import_private_headers}) +#################### + set(blockchain_export_sources blockchain_export.cpp bootstrap_file.cpp @@ -56,6 +60,7 @@ set(blockchain_export_private_headers monero_private_headers(blockchain_export ${blockchain_export_private_headers}) +#################### set(blockchain_blackball_sources blockchain_blackball.cpp @@ -70,6 +75,8 @@ set(blockchain_blackball_private_headers monero_private_headers(blockchain_blackball ${blockchain_blackball_private_headers}) +#################### + set(blockchain_usage_sources blockchain_usage.cpp ) @@ -79,7 +86,7 @@ set(blockchain_usage_private_headers) monero_private_headers(blockchain_usage ${blockchain_usage_private_headers}) - +#################### set(blockchain_prune_known_spent_data_sources blockchain_prune_known_spent_data.cpp @@ -90,7 +97,7 @@ set(blockchain_prune_known_spent_data_private_headers) monero_private_headers(blockchain_prune_known_spent_data ${blockchain_prune_known_spent_data_private_headers}) - +#################### set(blockchain_prune_sources blockchain_prune.cpp @@ -101,7 +108,7 @@ set(blockchain_prune_private_headers) monero_private_headers(blockchain_prune ${blockchain_prune_private_headers}) - +#################### set(blockchain_ancestry_sources blockchain_ancestry.cpp @@ -112,7 +119,7 @@ set(blockchain_ancestry_private_headers) monero_private_headers(blockchain_ancestry ${blockchain_ancestry_private_headers}) - +#################### set(blockchain_depth_sources blockchain_depth.cpp @@ -123,6 +130,8 @@ set(blockchain_depth_private_headers) monero_private_headers(blockchain_depth ${blockchain_depth_private_headers}) +#################### + set(blockchain_stats_sources blockchain_stats.cpp ) @@ -132,6 +141,18 @@ set(blockchain_stats_private_headers) monero_private_headers(blockchain_stats ${blockchain_stats_private_headers}) +#################### + +set(blockchain_find_locked_sources + blockchain_find_locked.cpp + ) + +set(blockchain_find_locked_private_headers) + +monero_private_headers(blockchain_find_locked + ${blockchain_find_locked_private_headers}) + +################################################################################ monero_add_executable(blockchain_import ${blockchain_import_sources} @@ -160,6 +181,8 @@ set_property(TARGET blockchain_import OUTPUT_NAME "monero-blockchain-import") install(TARGETS blockchain_import DESTINATION bin) +#################### + monero_add_executable(blockchain_export ${blockchain_export_sources} ${blockchain_export_private_headers}) @@ -181,6 +204,8 @@ set_property(TARGET blockchain_export OUTPUT_NAME "monero-blockchain-export") install(TARGETS blockchain_export DESTINATION bin) +#################### + monero_add_executable(blockchain_blackball ${blockchain_blackball_sources} ${blockchain_blackball_private_headers}) @@ -203,6 +228,7 @@ set_property(TARGET blockchain_blackball OUTPUT_NAME "monero-blockchain-mark-spent-outputs") install(TARGETS blockchain_blackball DESTINATION bin) +#################### monero_add_executable(blockchain_usage ${blockchain_usage_sources} @@ -225,6 +251,8 @@ set_property(TARGET blockchain_usage OUTPUT_NAME "monero-blockchain-usage") install(TARGETS blockchain_usage DESTINATION bin) +#################### + monero_add_executable(blockchain_ancestry ${blockchain_ancestry_sources} ${blockchain_ancestry_private_headers}) @@ -246,6 +274,8 @@ set_property(TARGET blockchain_ancestry OUTPUT_NAME "monero-blockchain-ancestry") install(TARGETS blockchain_ancestry DESTINATION bin) +#################### + monero_add_executable(blockchain_depth ${blockchain_depth_sources} ${blockchain_depth_private_headers}) @@ -267,6 +297,8 @@ set_property(TARGET blockchain_depth OUTPUT_NAME "monero-blockchain-depth") install(TARGETS blockchain_depth DESTINATION bin) +#################### + monero_add_executable(blockchain_stats ${blockchain_stats_sources} ${blockchain_stats_private_headers}) @@ -288,6 +320,31 @@ set_property(TARGET blockchain_stats OUTPUT_NAME "monero-blockchain-stats") install(TARGETS blockchain_stats DESTINATION bin) +#################### + +monero_add_executable(blockchain_find_locked + ${blockchain_find_locked_sources} + ${blockchain_find_locked_private_headers}) + +target_link_libraries(blockchain_find_locked + PRIVATE + cryptonote_core + blockchain_db + version + epee + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) + +set_property(TARGET blockchain_find_locked + PROPERTY + OUTPUT_NAME "monero-blockchain-find-locked") +install(TARGETS blockchain_find_locked DESTINATION bin) + +#################### + monero_add_executable(blockchain_prune_known_spent_data ${blockchain_prune_known_spent_data_sources} ${blockchain_prune_known_spent_data_private_headers}) @@ -310,6 +367,8 @@ set_property(TARGET blockchain_prune_known_spent_data OUTPUT_NAME "monero-blockchain-prune-known-spent-data") install(TARGETS blockchain_prune_known_spent_data DESTINATION bin) +#################### + monero_add_executable(blockchain_prune ${blockchain_prune_sources} ${blockchain_prune_private_headers}) @@ -331,3 +390,5 @@ target_link_libraries(blockchain_prune ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) + +################################################################################ diff --git a/src/blockchain_utilities/blockchain_find_locked.cpp b/src/blockchain_utilities/blockchain_find_locked.cpp new file mode 100644 index 000000000..bca859a06 --- /dev/null +++ b/src/blockchain_utilities/blockchain_find_locked.cpp @@ -0,0 +1,469 @@ +// Copyright (c) 2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "common/command_line.h" +#include "cryptonote_core/blockchain_and_pool.h" +#include "cryptonote_core/cryptonote_core.h" +#include "version.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" + +namespace po = boost::program_options; +using namespace epee; +using namespace cryptonote; + +static std::atomic stop_requested = false; + +enum class unlock_time_type_t +{ + Standard, // coinbase or unlock_time == 0 + NonStandardBlock, // non-coinbase and 0 < unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER + NonStandardUnix // non-coinbase and unlock_time >= CRYPTONOTE_MAX_BLOCK_NUMBER +}; + +struct report_slot_entry_t +{ + uint32_t num_standard_locks_opened; + uint32_t num_nonstandard_blocktime_locks_opened; + uint32_t num_nonstandard_unixtime_locks_opened; + uint32_t num_total_locks_opened; + + uint32_t num_standard_locks_closed; + uint32_t num_nonstandard_blocktime_locks_closed; + uint32_t num_nonstandard_unixtime_locks_closed; + uint32_t num_total_locks_closed; + + uint32_t standard_locks_balance; + uint32_t nonstandard_blocktime_locks_balance; + uint32_t nonstandard_unixtime_locks_balance; + uint32_t total_locks_balance; + + void tally_totals() + { + num_total_locks_opened = num_standard_locks_opened + + num_nonstandard_blocktime_locks_opened + + num_nonstandard_unixtime_locks_opened; + + num_total_locks_closed = num_standard_locks_closed + + num_nonstandard_blocktime_locks_closed + + num_nonstandard_unixtime_locks_closed; + + total_locks_balance = standard_locks_balance + + nonstandard_blocktime_locks_balance + + nonstandard_unixtime_locks_balance; + } + + void update_balances(uint32_t &standard_locks_balance_inout, + uint32_t &nonstandard_blocktime_locks_balance_inout, + uint32_t &nonstandard_unixtime_locks_balance_inout, + uint32_t &total_locks_balance_inout) + { + this->standard_locks_balance = standard_locks_balance_inout = + (standard_locks_balance_inout + this->num_standard_locks_opened - this->num_standard_locks_closed); + + this->nonstandard_blocktime_locks_balance = nonstandard_blocktime_locks_balance_inout = + (nonstandard_blocktime_locks_balance_inout + this->num_nonstandard_blocktime_locks_opened - + this->num_nonstandard_blocktime_locks_closed); + + this->nonstandard_unixtime_locks_balance = nonstandard_unixtime_locks_balance_inout = + (nonstandard_unixtime_locks_balance_inout + this->num_nonstandard_unixtime_locks_opened - + this->num_nonstandard_unixtime_locks_closed); + + this->total_locks_balance = total_locks_balance_inout = + (total_locks_balance_inout + this->num_total_locks_opened - this->num_total_locks_closed); + } +}; + +const std::string report_slot_entry_header = "num standard locks opened,num nonstandard blocktime locks opened," + "num nonstandard unixtime locks opened,num total locks opened,num standard locks closed," + "num nonstandard blocktime locks closed,num nonstandard unixtime locks closed,num total locks closed," + "standard locks balance,nonstandard blocktime locks balance,nonstandard unixtime locks balance,total locks balance"; + +std::ostream& operator<<(std::ostream &os, const report_slot_entry_t &report_slot_entry) +{ + os << report_slot_entry.num_standard_locks_opened; + os << ','; + os << report_slot_entry.num_nonstandard_blocktime_locks_opened; + os << ','; + os << report_slot_entry.num_nonstandard_unixtime_locks_opened; + os << ','; + os << report_slot_entry.num_total_locks_opened; + os << ','; + + os << report_slot_entry.num_standard_locks_closed; + os << ','; + os << report_slot_entry.num_nonstandard_blocktime_locks_closed; + os << ','; + os << report_slot_entry.num_nonstandard_unixtime_locks_closed; + os << ','; + os << report_slot_entry.num_total_locks_closed; + os << ','; + + os << report_slot_entry.standard_locks_balance; + os << ','; + os << report_slot_entry.nonstandard_blocktime_locks_balance; + os << ','; + os << report_slot_entry.nonstandard_unixtime_locks_balance; + os << ','; + os << report_slot_entry.total_locks_balance; + //os << ','; + + return os; +} + +static uint32_t get_first_spendable_block_index( + const uint32_t unlock_time, + const uint32_t block_included_in_chain) +{ + uint32_t unlock_block_index = 0; + + static_assert(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > 0, "unexpected default spendable age"); + const uint32_t default_block_index = block_included_in_chain + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + + if (unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + { + unlock_block_index = unlock_time; + } + else // unix timelock + { + // Interpret the unlock_time as time + // TODO: hardcode correct times for each network and take in nettype + const uint32_t hf_v15_time = 1656629118; + const uint32_t hf_v15_height = 2689608; + + // Use the last hard fork's time and block combo to convert the time-based timelock into an unlock block + // TODO: consider taking into account 60s block times when that was consensus + if (hf_v15_time > unlock_time) + { + const uint32_t seconds_since_unlock = hf_v15_time - unlock_time; + const uint32_t blocks_since_unlock = seconds_since_unlock / DIFFICULTY_TARGET_V2; + + unlock_block_index = hf_v15_height > blocks_since_unlock + ? (hf_v15_height - blocks_since_unlock) + : 0; + } + else + { + const uint32_t seconds_until_unlock = unlock_time - hf_v15_time; + const uint32_t blocks_until_unlock = seconds_until_unlock / DIFFICULTY_TARGET_V2; + unlock_block_index = hf_v15_height + blocks_until_unlock; + } + } + + // Can't unlock earlier than the default unlock block + return std::max(unlock_block_index, default_block_index); +} + +static uint32_t get_fcmp_tree_insertion_block_index( + const uint32_t unlock_time, + const uint32_t block_included_in_chain, + const bool is_coinbase, + const uint32_t ignore_after_block_index, + const uint32_t fcmp_activation_index) +{ + const uint32_t effective_unlock_time = + (is_coinbase || block_included_in_chain <= ignore_after_block_index) + ? unlock_time : 0; + + const uint32_t first_spendable_block_index = std::min( + get_first_spendable_block_index(effective_unlock_time, block_included_in_chain), + CRYPTONOTE_MAX_BLOCK_NUMBER); + + if (first_spendable_block_index <= fcmp_activation_index) + return block_included_in_chain; + else + return first_spendable_block_index; +} + +static unlock_time_type_t classify_unlock_time(const uint32_t unlock_time, const bool is_coinbase) +{ + if (unlock_time == 0 || is_coinbase) return unlock_time_type_t::Standard; + else if (unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) return unlock_time_type_t::NonStandardBlock; + else return unlock_time_type_t::NonStandardUnix; +} + +static bool is_coinbase_tx(epee::span tx_prefix_blob) +{ + uint32_t v; + int nread; + + // consume version + nread = tools::read_varint(tx_prefix_blob.data(), tx_prefix_blob.cend(), v); + if (nread < 0) + return false; + tx_prefix_blob.remove_prefix(nread); + + // consume unlock_time + nread = tools::read_varint(tx_prefix_blob.data(), tx_prefix_blob.cend(), v); + if (nread < 0) + return false; + tx_prefix_blob.remove_prefix(nread); + + // consume size of vin and expect 1 + nread = tools::read_varint(tx_prefix_blob.data(), tx_prefix_blob.cend(), v); + tx_prefix_blob.remove_prefix(nread); + if (v != 1 || nread < 0) + return false; + + // consume first vin type and expect txin_gen + if (tx_prefix_blob.empty()) + return false; + v = static_cast(tx_prefix_blob[0]); + tx_prefix_blob.remove_prefix(1); + if (v != 0xff) + return false; + + return true; +} + +int main(int argc, char* argv[]) +{ + epee::string_tools::set_module_name_and_folder(argv[0]); + + uint32_t log_level = 0; + + tools::on_startup(); + + tools::signal_handler::install([](int type) { + stop_requested.store(true); + }); + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + const command_line::arg_descriptor arg_log_level = {"log-level", "0-4 or categories", ""}; + const command_line::arg_descriptor arg_ignore_index = {"ignore-index", "ignore non-standard unlock times of outputs created after this block index", CRYPTONOTE_MAX_BLOCK_NUMBER}; + const command_line::arg_descriptor arg_fcmp_index = {"fcmp-index", + "if the calculated first spendable block index is less than or equal to this block index, simulate inserting immediately", 0}; + const command_line::arg_descriptor arg_output_file = {"output-file", "path to CSV output file", "blockchain-find-locked-output.csv"}; + + command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir); + command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on); + command_line::add_arg(desc_cmd_sett, cryptonote::arg_stagenet_on); + command_line::add_arg(desc_cmd_sett, arg_log_level); + command_line::add_arg(desc_cmd_sett, arg_ignore_index); + command_line::add_arg(desc_cmd_sett, arg_fcmp_index); + command_line::add_arg(desc_cmd_sett, arg_output_file); + 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::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + auto parser = po::command_line_parser(argc, argv).options(desc_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)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 1; + } + + mlog_configure(mlog_get_default_log_path("monero-blockchain-ancestry.log"), true); + if (!command_line::is_arg_defaulted(vm, arg_log_level)) + mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); + else + mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str()); + + LOG_PRINT_L0("Starting..."); + + std::string opt_data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir); + const bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); + const bool opt_stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on); + network_type net_type = opt_testnet ? TESTNET : opt_stagenet ? STAGENET : MAINNET; + const uint32_t opt_ignore_index = command_line::get_arg(vm, arg_ignore_index); + const uint32_t opt_fcmp_index = command_line::get_arg(vm, arg_fcmp_index); + const std::string opt_output_file = command_line::get_arg(vm, arg_output_file); + + if (std::filesystem::exists(opt_output_file)) + { + LOG_ERROR("Output file already exists"); + throw std::runtime_error("Output file already exists"); + } + + // If we wanted to use the memory pool, we would set up a fake_core. + + // Use Blockchain instead of lower-level BlockchainDB for ona main reason: + // Blockchain has the init() method for easy setup + // + // cannot match blockchain_storage setup above with just one line, + // e.g. + // Blockchain* core_storage = new Blockchain(NULL); + // because unlike blockchain_storage constructor, which takes a pointer to + // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. + LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); + std::unique_ptr core_storage = std::make_unique(); + BlockchainDB *db = new_db(); + if (db == NULL) + { + LOG_ERROR("Failed to initialize a database"); + throw std::runtime_error("Failed to initialize a database"); + } + LOG_PRINT_L0("database: LMDB"); + + const std::string filename = (std::filesystem::path(opt_data_dir) / db->get_db_name()).string(); + LOG_PRINT_L0("Loading blockchain from folder " << filename << " ..."); + + try + { + db->open(filename, DBF_RDONLY); + } + catch (const std::exception& e) + { + LOG_PRINT_L0("Error opening database: " << e.what()); + return 1; + } + r = core_storage->blockchain.init(db, net_type); + + CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); + LOG_PRINT_L0("Source blockchain storage initialized OK"); + + std::map report_by_block; + + LOG_PRINT_L0("Opening output file: " << opt_output_file); + std::ofstream report_ofs; + report_ofs.open(opt_output_file); + CHECK_AND_ASSERT_THROW_MES(!report_ofs.fail(), + "Could not open file '" << opt_output_file << "' for writing"); + + // get number of blocks and resize standard_cache_updates accordingly + const uint32_t db_height = db->height(); + + LOG_PRINT_L0("Blockchain height: " << db_height); + + LOG_PRINT_L0("Starting main output iteration loop..."); + uint32_t last_print_height = 0; + std::cout << "0 / " << db_height << "\r"; std::cout.flush(); + + // perform main data processing on all outputs + cryptonote::blobdata pruned_txblob; + db->for_all_outputs([&](const rct::xmr_amount amount, + const crypto::hash &tx_hash, + const uint32_t block_included_in_chain, + const size_t tx_idx, + const uint32_t unlock_time) -> bool + { + // determine if this output is a coinbase output + const bool is_potential_coinbase = unlock_time == block_included_in_chain + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + bool is_coinbase = false; + if (is_potential_coinbase) + { + CHECK_AND_ASSERT_THROW_MES(db->get_pruned_tx_blob(tx_hash, pruned_txblob), + "Pruned tx fetch fail"); + is_coinbase = is_coinbase_tx(epee::to_span(pruned_txblob)); + + if (!is_coinbase) + MDEBUG("fake coinbase at txid " << tx_hash); + } + + // calculate tree insertion index and unlock time type, sanity checking + const uint32_t tree_insertion_index = get_fcmp_tree_insertion_block_index(unlock_time, + block_included_in_chain, + is_coinbase, + opt_ignore_index, + opt_fcmp_index); + + CHECK_AND_ASSERT_THROW_MES(tree_insertion_index >= block_included_in_chain, + "get_fcmp_tree_insertion_block_index() trying to insert before creation"); + + // get references to relevant entries + report_slot_entry_t &open_entry = report_by_block[block_included_in_chain]; + report_slot_entry_t &close_entry = report_by_block[tree_insertion_index]; + + // depending on unlock_time_type, increment columns + const unlock_time_type_t unlock_time_type = classify_unlock_time(unlock_time, is_coinbase); + switch (unlock_time_type) + { + case unlock_time_type_t::Standard: + ++open_entry.num_standard_locks_opened; + ++close_entry.num_standard_locks_closed; + break; + case unlock_time_type_t::NonStandardBlock: + ++open_entry.num_nonstandard_blocktime_locks_opened; + ++close_entry.num_nonstandard_blocktime_locks_closed; + break; + case unlock_time_type_t::NonStandardUnix: + ++open_entry.num_nonstandard_unixtime_locks_opened; + ++close_entry.num_nonstandard_unixtime_locks_closed; + break; + } + + // print progress + if (amount == 0 && block_included_in_chain % 1000 == 0 && block_included_in_chain > last_print_height) + { + std::cout << block_included_in_chain << " / " << db_height << "\r"; std::cout.flush(); + last_print_height = block_included_in_chain; + } + + // goto next output if interrupt signal not received + return !stop_requested.load(); + } + ); + + LOG_PRINT_L0("Finished main output iteration loop. Cumulating results..."); + + uint32_t standard_locks_balance = 0; + uint32_t nonstandard_blocktime_locks_balance = 0; + uint32_t nonstandard_unixtime_locks_balance = 0; + uint32_t total_locks_balance = 0; + for (auto &p : report_by_block) + { + p.second.tally_totals(); + p.second.update_balances(standard_locks_balance, + nonstandard_blocktime_locks_balance, + nonstandard_unixtime_locks_balance, + total_locks_balance); + } + + LOG_PRINT_L0("Writing report to CSV file..."); + + report_ofs << "block_index," << report_slot_entry_header << "\n"; // header row + for (const auto &p : report_by_block) + { + report_ofs << p.first << ','; // block index col + report_ofs << p.second; // data columns + report_ofs << '\n'; // newline + } + report_ofs << '\n'; + + CHECK_AND_ASSERT_THROW_MES(!report_ofs.fail(), "writing CSV to output file failed"); + + LOG_PRINT_L0("Saved report.... Done!"); + + return 0; +} diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 13c470172..8edada8a9 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5592,16 +5592,6 @@ bool Blockchain::for_all_transactions(std::functionfor_all_transactions(f, pruned); } -bool Blockchain::for_all_outputs(std::function f) const -{ - return m_db->for_all_outputs(f); -} - -bool Blockchain::for_all_outputs(uint64_t amount, std::function f) const -{ - return m_db->for_all_outputs(amount, f); -} - void Blockchain::invalidate_block_template_cache() { MDEBUG("Invalidating block template cache"); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 2caad16a5..75ee89b65 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -978,25 +978,6 @@ namespace cryptonote */ bool for_all_transactions(std::function, bool pruned) const; - /** - * @brief perform a check on all outputs in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any output fails the check, otherwise true - */ - bool for_all_outputs(std::function) const; - - /** - * @brief perform a check on all outputs of a given amount in the blockchain - * - * @param amount the amount to iterate through - * @param std::function the check to perform, pass/fail - * - * @return false if any output fails the check, otherwise true - */ - bool for_all_outputs(uint64_t amount, std::function) const; - /** * @brief get a reference to the BlockchainDB in use by Blockchain *