From 0750adf50443b5f29097424831ca7e5879c7a2fa Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 10 Dec 2024 20:19:24 -0500 Subject: [PATCH 1/2] Factor bn executor. --- Makefile.am | 10 + builds/cmake/CMakeLists.txt | 10 + builds/msvc/vs2022/bn/bn.vcxproj | 10 + builds/msvc/vs2022/bn/bn.vcxproj.filters | 30 + console/executor.cpp | 2658 +--------------------- console/executor.hpp | 78 +- console/executor_commands.cpp | 263 +++ console/executor_dumps.cpp | 187 ++ console/executor_events.cpp | 92 + console/executor_logging.cpp | 113 + console/executor_options.cpp | 363 +++ console/executor_runner.cpp | 183 ++ console/executor_scans.cpp | 468 ++++ console/executor_store.cpp | 271 +++ console/executor_test_reader.cpp | 845 +++++++ console/executor_test_writer.cpp | 235 ++ console/main.cpp | 4 +- console/stack_trace.cpp | 8 +- 18 files changed, 3176 insertions(+), 2652 deletions(-) create mode 100644 console/executor_commands.cpp create mode 100644 console/executor_dumps.cpp create mode 100644 console/executor_events.cpp create mode 100644 console/executor_logging.cpp create mode 100644 console/executor_options.cpp create mode 100644 console/executor_runner.cpp create mode 100644 console/executor_scans.cpp create mode 100644 console/executor_store.cpp create mode 100644 console/executor_test_reader.cpp create mode 100644 console/executor_test_writer.cpp diff --git a/Makefile.am b/Makefile.am index 276b7c261..65bab0657 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,6 +112,16 @@ console_bn_LDADD = src/libbitcoin-node.la ${bitcoin_database_LIBS} ${bitcoin_net console_bn_SOURCES = \ console/executor.cpp \ console/executor.hpp \ + console/executor_commands.cpp \ + console/executor_dumps.cpp \ + console/executor_events.cpp \ + console/executor_logging.cpp \ + console/executor_options.cpp \ + console/executor_runner.cpp \ + console/executor_scans.cpp \ + console/executor_store.cpp \ + console/executor_test_reader.cpp \ + console/executor_test_writer.cpp \ console/localize.hpp \ console/main.cpp \ console/stack_trace.cpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 8e7b2f966..5c0c2a049 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -367,6 +367,16 @@ if (with-console) add_executable( bn "../../console/executor.cpp" "../../console/executor.hpp" + "../../console/executor_commands.cpp" + "../../console/executor_dumps.cpp" + "../../console/executor_events.cpp" + "../../console/executor_logging.cpp" + "../../console/executor_options.cpp" + "../../console/executor_runner.cpp" + "../../console/executor_scans.cpp" + "../../console/executor_store.cpp" + "../../console/executor_test_reader.cpp" + "../../console/executor_test_writer.cpp" "../../console/libbitcoin.ico" "../../console/localize.hpp" "../../console/main.cpp" diff --git a/builds/msvc/vs2022/bn/bn.vcxproj b/builds/msvc/vs2022/bn/bn.vcxproj index 3e6f49459..745034e7b 100644 --- a/builds/msvc/vs2022/bn/bn.vcxproj +++ b/builds/msvc/vs2022/bn/bn.vcxproj @@ -71,6 +71,16 @@ + + + + + + + + + + diff --git a/builds/msvc/vs2022/bn/bn.vcxproj.filters b/builds/msvc/vs2022/bn/bn.vcxproj.filters index 202121852..9211de6ed 100644 --- a/builds/msvc/vs2022/bn/bn.vcxproj.filters +++ b/builds/msvc/vs2022/bn/bn.vcxproj.filters @@ -18,6 +18,36 @@ src + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + src diff --git a/console/executor.cpp b/console/executor.cpp index 4c3dbbc1f..d96ae2267 100644 --- a/console/executor.cpp +++ b/console/executor.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -30,2612 +30,74 @@ #include #include -// This file is just an ad-hoc user interface wrapper on the node. -// It will be factored and cleaned up for final release. - -namespace libbitcoin { -namespace node { - -using boost::format; -using system::config::printer; -using namespace network; -using namespace system; -using namespace std::chrono; -using namespace std::placeholders; - -// for --help only -const std::string executor::name_{ "bn" }; - -// for capture only -const std::string executor::close_{ "c" }; - -const std::unordered_map executor::options_ -{ - { "b", menu::backup }, - { "c", menu::close }, - { "e", menu::errors }, - { "g", menu::go }, - { "h", menu::hold }, - { "i", menu::info }, - { "m", menu::menu_ }, - { "t", menu::test }, - { "w", menu::work }, - { "z", menu::zeroize } -}; -const std::unordered_map executor::options_menu_ -{ - { menu::backup, "[b]ackup the store" }, - { menu::close, "[c]lose the node" }, - { menu::errors, "[e]rrors in store" }, - { menu::go, "[g]o network communication" }, - { menu::hold, "[h]old network communication" }, - { menu::info, "[i]nfo about store" }, - { menu::menu_, "[m]enu of options and toggles" }, - { menu::test, "[t]est built-in case" }, - { menu::work, "[w]ork distribution" }, - { menu::zeroize, "[z]eroize disk full error" } -}; -const std::unordered_map executor::toggles_ -{ - { "a", levels::application }, - { "n", levels::news }, - { "s", levels::session }, - { "p", levels::protocol }, - { "x", levels::proxy }, - { "r", levels::remote }, - { "f", levels::fault }, - { "q", levels::quitting }, - { "o", levels::objects }, - { "v", levels::verbose } -}; -const std::unordered_map executor::toggles_menu_ -{ - { levels::application, "[a]pplication" }, - { levels::news, "[n]ews" }, - { levels::session, "[s]ession" }, - { levels::protocol, "[p]rotocol" }, - { levels::proxy, "[x]proxy" }, - { levels::remote, "[r]emote" }, - { levels::fault, "[f]ault" }, - { levels::quitting, "[q]uitting" }, - { levels::objects, "[o]bjects" }, - { levels::verbose, "[v]erbose" } -}; -const std::unordered_map executor::defined_ -{ - { levels::application, levels::application_defined }, - { levels::news, levels::news_defined }, - { levels::session, levels::session_defined }, - { levels::protocol, levels::protocol_defined }, - { levels::proxy, levels::proxy_defined }, - { levels::remote, levels::remote_defined }, - { levels::fault, levels::fault_defined }, - { levels::quitting, levels::quitting_defined }, - { levels::objects, levels::objects_defined }, - { levels::verbose, levels::verbose_defined }, -}; -const std::unordered_map executor::fired_ -{ - { events::header_archived, "header_archived....." }, - { events::header_organized, "header_organized...." }, - { events::header_reorganized, "header_reorganized.." }, - - { events::block_archived, "block_archived......" }, - { events::block_buffered, "block_buffered......" }, - { events::block_validated, "block_validated....." }, - { events::block_confirmed, "block_confirmed....." }, - { events::block_unconfirmable, "block_unconfirmable." }, - { events::validate_bypassed, "validate_bypassed..." }, - { events::confirm_bypassed, "confirm_bypassed...." }, - - { events::tx_archived, "tx_archived........." }, - { events::tx_validated, "tx_validated........" }, - { events::tx_invalidated, "tx_invalidated......" }, - - { events::block_organized, "block_organized....." }, - { events::block_reorganized, "block_reorganized..." }, - - { events::template_issued, "template_issued....." }, - - { events::snapshot_span, "snapshot_span......." } -}; - -// non-const member static (global for blocking interrupt handling). -std::promise executor::stopping_{}; - -// non-const member static (global for non-blocking interrupt handling). -std::atomic_bool executor::cancel_{}; - -executor::executor(parser& metadata, std::istream& input, std::ostream& output, - std::ostream&) - : metadata_(metadata), - store_(metadata.configured.database), - query_(store_), - input_(input), - output_(output), - toggle_ - { - metadata.configured.log.application, - metadata.configured.log.news, - metadata.configured.log.session, - metadata.configured.log.protocol, - metadata.configured.log.proxy, - metadata.configured.log.remote, - metadata.configured.log.fault, - metadata.configured.log.quitting, - metadata.configured.log.objects, - metadata.configured.log.verbose - } -{ - // Capture . - initialize_stop(); -} - -// Utility. -// ---------------------------------------------------------------------------- - -void executor::logger(const auto& message) const -{ - if (log_.stopped()) - output_ << message << std::endl; - else - log_.write(levels::application) << message << std::endl; -}; - -void executor::stopper(const auto& message) -{ - capture_.stop(); - log_.stop(message, levels::application); - stopped_.get_future().wait(); -} - -// Measures. -// ---------------------------------------------------------------------------- - -void executor::dump_body_sizes() const -{ - logger(format(BN_MEASURE_SIZES) % - query_.header_body_size() % - query_.txs_body_size() % - query_.tx_body_size() % - query_.point_body_size() % - query_.input_body_size() % - query_.output_body_size() % - query_.puts_body_size() % - query_.candidate_body_size() % - query_.confirmed_body_size() % - query_.spend_body_size() % - query_.strong_tx_body_size() % - query_.validated_tx_body_size() % - query_.validated_bk_body_size() % - query_.address_body_size() % - query_.neutrino_body_size()); -} - -void executor::dump_records() const -{ - logger(format(BN_MEASURE_RECORDS) % - query_.header_records() % - query_.tx_records() % - query_.point_records() % - query_.candidate_records() % - query_.confirmed_records() % - query_.spend_records() % - query_.strong_tx_records() % - query_.address_records()); -} - -void executor::dump_buckets() const -{ - logger(format(BN_MEASURE_BUCKETS) % - query_.header_buckets() % - query_.txs_buckets() % - query_.tx_buckets() % - query_.point_buckets() % - query_.spend_buckets() % - query_.strong_tx_buckets() % - query_.validated_tx_buckets() % - query_.validated_bk_buckets() % - query_.address_buckets() % - query_.neutrino_buckets()); -} - -void executor::dump_progress() const -{ - logger(format(BN_MEASURE_PROGRESS) % - query_.get_fork() % - query_.get_top_confirmed() % - encode_hash(query_.get_header_key(query_.to_confirmed( - query_.get_top_confirmed()))) % - query_.get_top_candidate() % - encode_hash(query_.get_header_key(query_.to_candidate( - query_.get_top_candidate()))) % - query_.get_top_associated() % - (query_.get_top_candidate() - query_.get_unassociated_count()) % - query_.get_confirmed_size() % - query_.get_candidate_size()); -} - -// txs, validated_tx, validated_bk collision rates assume 1:1 records. -void executor::dump_collisions() const -{ - logger(format(BN_MEASURE_COLLISION_RATES) % - ((1.0 * query_.header_records()) / query_.header_buckets()) % - ((1.0 * query_.header_records()) / query_.txs_buckets()) % - ((1.0 * query_.tx_records()) / query_.tx_buckets()) % - ((1.0 * query_.point_records()) / query_.point_buckets()) % - ((1.0 * query_.spend_records()) / query_.spend_buckets()) % - ((1.0 * query_.strong_tx_records()) / query_.strong_tx_buckets()) % - ((1.0 * query_.tx_records()) / query_.validated_tx_buckets()) % - ((1.0 * query_.header_records()) / query_.validated_bk_buckets()) % - (query_.address_enabled() ? ((1.0 * query_.address_records()) / - query_.address_buckets()) : 0) % - (query_.neutrino_enabled() ? ((1.0 * query_.header_records()) / - query_.neutrino_buckets()) : 0)); -} - -// logging compilation and initial values. -void executor::dump_options() const -{ - logger(BN_NODE_INTERRUPT); - logger(BN_LOG_TABLE_HEADER); - logger(format("[a]pplication.. " BN_LOG_TABLE) % levels::application_defined % toggle_.at(levels::application)); - logger(format("[n]ews......... " BN_LOG_TABLE) % levels::news_defined % toggle_.at(levels::news)); - logger(format("[s]ession...... " BN_LOG_TABLE) % levels::session_defined % toggle_.at(levels::session)); - logger(format("[p]rotocol..... " BN_LOG_TABLE) % levels::protocol_defined % toggle_.at(levels::protocol)); - logger(format("[x]proxy....... " BN_LOG_TABLE) % levels::proxy_defined % toggle_.at(levels::proxy)); - logger(format("[r]emote....... " BN_LOG_TABLE) % levels::remote_defined % toggle_.at(levels::remote)); - logger(format("[f]ault........ " BN_LOG_TABLE) % levels::fault_defined % toggle_.at(levels::fault)); - logger(format("[q]uitting..... " BN_LOG_TABLE) % levels::quitting_defined % toggle_.at(levels::quitting)); - logger(format("[o]bjects...... " BN_LOG_TABLE) % levels::objects_defined % toggle_.at(levels::objects)); - logger(format("[v]erbose...... " BN_LOG_TABLE) % levels::verbose_defined % toggle_.at(levels::verbose)); -} - -// emit version information for libbitcoin libraries -void executor::dump_version() const -{ - logger(format(BN_VERSION_MESSAGE) - % LIBBITCOIN_NODE_VERSION - % LIBBITCOIN_DATABASE_VERSION - % LIBBITCOIN_NETWORK_VERSION - % LIBBITCOIN_SYSTEM_VERSION); -} - -// fork flag transitions (candidate chain). -void executor::scan_flags() const -{ - const auto start = logger::now(); - constexpr auto flag_bits = to_bits(sizeof(chain::flags)); - const auto error = code{ database::error::integrity }.message(); - const auto top = query_.get_top_candidate(); - uint32_t flags{}; - - logger(BN_OPERATION_INTERRUPT); - - for (size_t height{}; !cancel_ && height <= top; ++height) - { - database::context ctx{}; - const auto link = query_.to_candidate(height); - if (!query_.get_context(ctx, link) || (ctx.height != height)) - { - logger(format("Error: %1%") % error); - return; - } - - if (ctx.flags != flags) - { - const binary prev{ flag_bits, to_big_endian(flags) }; - const binary next{ flag_bits, to_big_endian(ctx.flags) }; - logger(format("Forked from [%1%] to [%2%] at [%3%:%4%]") % prev % - next % encode_hash(query_.get_header_key(link)) % height); - flags = ctx.flags; - } - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - const auto span = duration_cast(logger::now() - start); - logger(format("Scanned %1% headers for rule forks in %2% ms.") % top % - span.count()); -} - -// file and logical sizes. -void executor::measure_size() const -{ - dump_body_sizes(); - dump_records(); - dump_buckets(); - dump_collisions(); - - // This one can take a few seconds on cold iron. - logger(BN_MEASURE_PROGRESS_START); - dump_progress(); -} - -// input and output table slab counts. -void executor::scan_slabs() const -{ - logger(BN_MEASURE_SLABS); - logger(BN_OPERATION_INTERRUPT); - database::tx_link::integer link{}; - size_t inputs{}, outputs{}; - const auto start = logger::now(); - constexpr auto frequency = 100'000; - - // Tx (record) links are sequential and so iterable, however the terminal - // condition assumes all tx entries fully written (ok for stopped node). - // A running node cannot safely iterate over record links, but stopped can. - for (auto puts = query_.put_counts(link); to_bool(puts.first) && !cancel_; - puts = query_.put_counts(++link)) - { - inputs += puts.first; - outputs += puts.second; - if (is_zero(link % frequency)) - logger(format(BN_MEASURE_SLABS_ROW) % link % inputs % outputs); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - const auto span = duration_cast(logger::now() - start); - logger(format(BN_MEASURE_STOP) % inputs % outputs % span.count()); -} - -// hashmap bucket fill rates. -void executor::scan_buckets() const -{ - constexpr auto block_frequency = 10'000u; - constexpr auto tx_frequency = 1'000'000u; - constexpr auto put_frequency = 10'000'000u; - - logger(BN_OPERATION_INTERRUPT); - - auto filled = zero; - auto bucket = max_size_t; - auto start = logger::now(); - while (!cancel_ && (++bucket < query_.header_buckets())) - { - const auto top = query_.top_header(bucket); - if (!top.is_terminal()) - ++filled; - - if (is_zero(bucket % block_frequency)) - logger(format("header" BN_READ_ROW) % bucket % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - auto span = duration_cast(logger::now() - start); - logger(format("header" BN_READ_ROW) % (1.0 * filled / bucket) % - span.count()); - - // ------------------------------------------------------------------------ - - filled = zero; - bucket = max_size_t; - start = logger::now(); - while (!cancel_ && (++bucket < query_.txs_buckets())) - { - const auto top = query_.top_txs(bucket); - if (!top.is_terminal()) - ++filled; - - if (is_zero(bucket % block_frequency)) - logger(format("txs" BN_READ_ROW) % bucket % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - span = duration_cast(logger::now() - start); - logger(format("txs" BN_READ_ROW) % (1.0 * filled / bucket) % - span.count()); - - // ------------------------------------------------------------------------ - - filled = zero; - bucket = max_size_t; - start = logger::now(); - while (!cancel_ && (++bucket < query_.tx_buckets())) - { - const auto top = query_.top_tx(bucket); - if (!top.is_terminal()) - ++filled; - - if (is_zero(bucket % tx_frequency)) - logger(format("tx" BN_READ_ROW) % bucket % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - span = duration_cast(logger::now() - start); - logger(format("tx" BN_READ_ROW) % (1.0 * filled / bucket) % - span.count()); - - // ------------------------------------------------------------------------ - - filled = zero; - bucket = max_size_t; - start = logger::now(); - while (!cancel_ && (++bucket < query_.point_buckets())) - { - const auto top = query_.top_point(bucket); - if (!top.is_terminal()) - ++filled; - - if (is_zero(bucket % tx_frequency)) - logger(format("point" BN_READ_ROW) % bucket % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - span = duration_cast(logger::now() - start); - logger(format("point" BN_READ_ROW) % (1.0 * filled / bucket) % - span.count()); - - // ------------------------------------------------------------------------ - - filled = zero; - bucket = max_size_t; - start = logger::now(); - while (!cancel_ && (++bucket < query_.spend_buckets())) - { - const auto top = query_.top_spend(bucket); - if (!top.is_terminal()) - ++filled; - - if (is_zero(bucket % put_frequency)) - logger(format("spend" BN_READ_ROW) % bucket % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - span = duration_cast(logger::now() - start); - logger(format("spend" BN_READ_ROW) % (1.0 * filled / bucket) % - span.count()); -} - -// hashmap collision distributions. -// BUGBUG: the vector allocations are exceessive and can result in sigkill. -// BUGBUG: must process each header independently as buckets may not coincide. -void executor::scan_collisions() const -{ - using namespace database; - using hint = header_link::integer; - constexpr auto empty = 0u; - constexpr auto block_frequency = 10'000u; - constexpr auto tx_frequency = 1'000'000u; - constexpr auto put_frequency = 10'000'000u; - constexpr auto count = [](const auto& list) - { - return std::accumulate(list.begin(), list.end(), zero, - [](size_t total, const auto& value) - { - return total + to_int(to_bool(value)); - }); - }; - constexpr auto dump = [&](const auto& list) - { - // map frequency to length. - std::map map{}; - for (const auto value: list) - ++map[value]; - - return map; - }; - constexpr auto floater = [](const auto value) - { - return 1.0 * value; - }; - constexpr auto hash = [](const auto& key) - { - constexpr auto length = array_count; - constexpr auto size = std::min(length, sizeof(size_t)); - size_t value{}; - std::copy_n(key.begin(), size, system::byte_cast(value).begin()); - return value; - }; - - logger(BN_OPERATION_INTERRUPT); - - // header & txs (txs is a proxy for validated_bk) - // ------------------------------------------------------------------------ - - auto index = max_size_t; - auto start = logger::now(); - const auto header_buckets = query_.header_buckets(); - const auto header_records = query_.header_records(); - std_vector header(header_buckets, empty); - std_vector txs(header_buckets, empty); - while (!cancel_ && (++index < header_records)) - { - const header_link link{ possible_narrow_cast(index) }; - ++header.at(hash(query_.get_header_key(link.value)) % header_buckets); - ++txs.at(hash((header_link::bytes)link) % header_buckets); - - if (is_zero(index % block_frequency)) - logger(format("header/txs" BN_READ_ROW) % index % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - // ........................................................................ - - const auto header_count = count(header); - auto span = duration_cast(logger::now() - start); - logger(format("header: %1% in %2%s buckets %3% filled %4% rate %5% ") % - index % span.count() % header_buckets % header_count % - (floater(header_count) / header_buckets)); - - for (const auto& entry: dump(header)) - logger(format("header: %1% frequency: %2%") % - entry.first % entry.second); - - header.clear(); - header.shrink_to_fit(); - - // ........................................................................ - - const auto txs_count = count(txs); - span = duration_cast(logger::now() - start); - logger(format("txs: %1% in %2%s buckets %3% filled %4% rate %5%") % - index % span.count() % header_buckets % txs_count % - (floater(txs_count) / header_buckets)); - - for (const auto& entry: dump(txs)) - logger(format("txs: %1% frequency: %2%") % - entry.first % entry.second); - - txs.clear(); - txs.shrink_to_fit(); - - // tx & strong_tx (strong_tx is a proxy for validated_tx) - // ------------------------------------------------------------------------ - - index = max_size_t; - start = logger::now(); - const auto tx_buckets = query_.tx_buckets(); - const auto tx_records = query_.tx_records(); - std_vector tx(tx_buckets, empty); - std_vector strong_tx(tx_buckets, empty); - while (!cancel_ && (++index < tx_records)) - { - const tx_link link{ possible_narrow_cast(index) }; - ++tx.at(hash(query_.get_tx_key(link.value)) % tx_buckets); - ++strong_tx.at(hash((tx_link::bytes)link) % tx_buckets); - - if (is_zero(index % tx_frequency)) - logger(format("tx & strong_tx" BN_READ_ROW) % index % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - // ........................................................................ - - const auto tx_count = count(tx); - span = duration_cast(logger::now() - start); - logger(format("tx: %1% in %2%s buckets %3% filled %4% rate %5%") % - index % span.count() % tx_buckets % tx_count % - (floater(tx_count) / tx_buckets)); - - for (const auto& entry: dump(tx)) - logger(format("tx: %1% frequency: %2%") % - entry.first % entry.second); - - tx.clear(); - tx.shrink_to_fit(); - - // ........................................................................ - - const auto strong_tx_count = count(strong_tx); - span = duration_cast(logger::now() - start); - logger(format("strong_tx: %1% in %2%s buckets %3% filled %4% rate %5%") % - index % span.count() % tx_buckets % strong_tx_count % - (floater(strong_tx_count) / tx_buckets)); - - for (const auto& entry: dump(strong_tx)) - logger(format("strong_tx: %1% frequency: %2%") % - entry.first % entry.second); - - strong_tx.clear(); - strong_tx.shrink_to_fit(); - - // point - // ------------------------------------------------------------------------ - - index = max_size_t; - start = logger::now(); - const auto point_buckets = query_.point_buckets(); - const auto point_records = query_.point_records(); - std_vector point(point_buckets, empty); - while (!cancel_ && (++index < point_records)) - { - const tx_link link{ possible_narrow_cast(index) }; - ++point.at(hash(query_.get_point_key(link.value)) % point_buckets); - - if (is_zero(index % tx_frequency)) - logger(format("point" BN_READ_ROW) % index % - duration_cast(logger::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - // ........................................................................ - - const auto point_count = count(point); - span = duration_cast(logger::now() - start); - logger(format("point: %1% in %2%s buckets %3% filled %4% rate %5%") % - index % span.count() % point_buckets % point_count % - (floater(point_count) / point_buckets)); - - for (const auto& entry: dump(point)) - logger(format("point: %1% frequency: %2%") % - entry.first % entry.second); - - point.clear(); - point.shrink_to_fit(); - - // spend - // ------------------------------------------------------------------------ - - auto total = zero; - index = max_size_t; - start = logger::now(); - const auto spend_buckets = query_.spend_buckets(); - std_vector spend(spend_buckets, empty); - while (!cancel_ && (++index < query_.header_records())) - { - const header_link link{ possible_narrow_cast(index) }; - const auto transactions = query_.to_transactions(link); - for (const auto& transaction: transactions) - { - const auto inputs = query_.to_tx_spends(transaction); - for (const auto& in: inputs) - { - ++total; - ++spend.at(hash(query_.to_spend_key(in)) % spend_buckets); - - if (is_zero(index % put_frequency)) - logger(format("spend" BN_READ_ROW) % total % - duration_cast(logger::now() - start).count()); - } - } - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - // ........................................................................ - - const auto spend_count = count(spend); - span = duration_cast(logger::now() - start); - logger(format("spend: %1% in %2%s buckets %3% filled %4% rate %5%") % - total % span.count() % spend_buckets % spend_count % - (floater(spend_count) / spend_buckets)); - - for (const auto& entry: dump(spend)) - logger(format("spend: %1% frequency: %2%") % - entry.first % entry.second); - - spend.clear(); - spend.shrink_to_fit(); -} - -void executor::read_test(bool dump) const -{ - constexpr auto start_tx = 1'000'000'000_u32; - constexpr auto target_count = 100_size; - - // Set ensures unique addresses. - std::set keys{}; - auto tx = start_tx; - - logger(format("Getting first [%1%] output address hashes.") % target_count); - - auto start = fine_clock::now(); - while (!cancel_ && keys.size() < target_count) - { - const auto outputs = query_.get_outputs(tx++); - if (outputs->empty()) - return; - - for (const auto& put: *outputs) - { - keys.emplace(put->script().hash()); - if (cancel_ || keys.size() == target_count) - break; - } - } - - auto span = duration_cast(fine_clock::now() - start); - logger(format("Got first [%1%] unique addresses above tx [%2%] in [%3%] ms.") % - keys.size() % start_tx % span.count()); - - struct out - { - hash_digest address; - - uint32_t bk_fk; - uint32_t bk_height; - hash_digest bk_hash; - - uint32_t tx_fk; - size_t tx_position; - hash_digest tx_hash; - - uint32_t sp_tx_fk; - hash_digest sp_tx_hash; - - uint64_t input_fk; - chain::input::cptr input{}; - - uint64_t output_fk; - chain::output::cptr output{}; - }; - - std_vector outs{}; - outs.reserve(target_count); - using namespace database; - - start = fine_clock::now(); - for (auto& key: keys) - { - size_t found{}; - auto address_it = store_.address.it(key); - if (cancel_ || address_it.self().is_terminal()) - return; - - do - { - table::address::record address{}; - if (cancel_ || !store_.address.get(address_it.self(), address)) - return; - - const auto out_fk = address.output_fk; - table::output::get_parent output{}; - if (!store_.output.get(out_fk, output)) - return; - - const auto tx_fk = output.parent_fk; - const auto block_fk = query_.to_block(tx_fk); - - table::header::get_height header{}; - if (!store_.header.get(block_fk, header)) - return; - - table::txs::get_position txs{ {}, tx_fk }; - if (!store_.txs.get(query_.to_txs(block_fk), txs)) - return; - - spend_link sp_fk{}; - input_link in_fk{}; - tx_link sp_tx_fk{}; - - // Get first spender only (may or may not be confirmed). - const auto spenders = query_.to_spenders(out_fk); - if (!spenders.empty()) - { - sp_fk = spenders.front(); - table::spend::record spend{}; - if (!store_.spend.get(sp_fk, spend)) - return; - - in_fk = spend.input_fk; - sp_tx_fk = spend.parent_fk; - } - - ++found; - outs.push_back(out - { - key, - - block_fk, - header.height, - query_.get_header_key(block_fk), - - tx_fk, - txs.position, - query_.get_tx_key(tx_fk), - - sp_tx_fk, - query_.get_tx_key(sp_tx_fk), - - in_fk, - query_.get_input(sp_fk), - - out_fk, - query_.get_output(out_fk) - }); - } - while (address_it.advance()); - - logger(format("Fetched [%1%] unique payments to address [%2%].") % - found% encode_hash(key)); - } - - span = duration_cast(fine_clock::now() - start); - logger(format("Got all [%1%] payments to [%2%] addresses in [%3%] ms.") % - outs.size() % keys.size() % span.count()); - - if (!dump) - return; - - // Write it all... - logger( - "output_script_hash, " - - "ouput_bk_fk, " - "ouput_bk_height, " - "ouput_bk_hash, " - - "ouput_tx_fk, " - "ouput_tx_position, " - "ouput_tx_hash, " - - "input_tx_fk, " - "input_tx_hash, " - - "output_fk, " - "output_script, " - - "input_fk, " - "input_script" - ); - - for (const auto& row: outs) - { - if (cancel_) break; - - const auto output = !row.output ? "{error}" : - row.output->script().to_string(chain::flags::all_rules); - - const auto input = !row.input ? "{unspent}" : - row.input->script().to_string(chain::flags::all_rules); - - logger(format("%1%, %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%, %11%, %12%, %13%") % - encode_hash(row.address) % - - row.bk_fk % - row.bk_height % - encode_hash(row.bk_hash) % - - row.tx_fk % - row.tx_position % - encode_hash(row.tx_hash) % - - row.sp_tx_fk % - encode_hash(row.sp_tx_hash) % - - row.output_fk % - output % - - row.input_fk % - input); - } -} - -#if defined(UNDEFINED) - -// arbitrary testing (const). -void executor::read_test() const -{ - logger("Wire size computation."); - const auto start = fine_clock::now(); - const auto last = metadata_.configured.node.maximum_height; - - size_t size{}; - for (auto height = zero; !cancel_ && height <= last; ++height) - { - const auto link = query_.to_candidate(height); - if (link.is_terminal()) - { - logger(format("Max candidate height is (%1%).") % sub1(height)); - return; - } - - const auto bytes = query_.get_block_size(link); - if (is_zero(bytes)) - { - logger(format("Block (%1%) is not associated.") % height); - return; - } - - size += bytes; - } - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("Wire size (%1%) at (%2%) in (%3%) ms.") % - size % last % span.count()); -} - -void executor::read_test() const -{ - auto start = fine_clock::now(); - auto count = query_.header_records(); - uint32_t block{ one }; - - logger("Find strong blocks."); - while (!cancel_ && (block < count) && query_.is_strong_block(block)) - { - ++block; - } - - auto span = duration_cast(fine_clock::now() - start); - logger(format("Top strong block is [%1%] in [%2%] ms.") % sub1(block) % span.count()); - start = fine_clock::now(); - count = query_.header_records(); - uint32_t milestone{ 295'001 }; - - logger("Find milestone blocks."); - while (!cancel_ && (milestone < count) && query_.is_milestone(milestone)) - { - ++milestone; - } - - span = duration_cast(fine_clock::now() - start); - logger(format("Top milestone block is [%1%] in [%2%] ms.") % sub1(milestone) % span.count()); - start = fine_clock::now(); - uint32_t tx{ one }; - - logger("Find strong txs."); - count = query_.tx_records(); - while (!cancel_ && (tx < count) && query_.is_strong_tx(tx)) - { - ++tx; - } - - span = duration_cast(fine_clock::now() - start); - logger(format("Top strong tx is [%1%] in [%2%] ms.") % sub1(tx) % span.count()); -} - -void executor::read_test() const -{ - const auto from = 481'824_u32; - const auto top = 840'000_u32; ////query_.get_top_associated(); - const auto start = fine_clock::now(); - - // segwit activation - uint32_t block{ from }; - size_t total{}; - - logger("Get all coinbases."); - while (!cancel_ && (block <= top)) - { - const auto count = query_.get_tx_count(query_.to_candidate(block++)); - if (is_zero(count)) - return; - - total += system::ceilinged_log2(count); - } - - const auto average = total / (top - from); - const auto span = duration_cast(fine_clock::now() - start); - logger(format("Total block depths [%1%] to [%2%] avg [%3%] in [%4%] ms.") - % total % top % average % span.count()); -} - -void executor::read_test() const -{ - // Binance wallet address with 1,380,169 transaction count. - // blockstream.info/address/bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h - const auto data = base16_array("0014dc6bf86354105de2fcd9868a2b0376d6731cb92f"); - const chain::script output_script{ data, false }; - const auto mnemonic = output_script.to_string(chain::flags::all_rules); - logger(format("Getting payments to {%1%}.") % mnemonic); - - const auto start = fine_clock::now(); - database::output_links outputs{}; - if (!query_.to_address_outputs(outputs, output_script.hash())) - return; - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("Found [%1%] outputs of {%2%} in [%3%] ms.") % - outputs.size() % mnemonic % span.count()); -} - -// This was caused by concurrent redundant downloads at tail following restart. -// The earlier transactions were marked as confirmed and during validation the -// most recent are found via point.hash association priot to to_block() test. -void executor::read_test() const -{ - const auto height = 839'287_size; - const auto block = query_.to_confirmed(height); - if (block.is_terminal()) - { - logger("!block"); - return; - } - - const auto txs = query_.to_transactions(block); - if (txs.empty()) - { - logger("!txs"); - return; - } - - database::tx_link spender_link{}; - const auto hash_spender = system::base16_hash("1ff970ec310c000595929bd290bbc8f4603ee18b2b4e3239dfb072aaca012b28"); - for (auto position = zero; !cancel_ && position < txs.size(); ++position) - { - const auto temp = txs.at(position); - if (query_.get_tx_key(temp) == hash_spender) - { - spender_link = temp; - break; - } - } - - auto spenders = store_.tx.it(hash_spender); - if (spenders.self().is_terminal()) - return; - - // ...260, 261 - size_t spender_count{}; - do - { - const auto foo = spenders.self(); - ++spender_count; - } while(spenders.advance()); - - if (is_zero(spender_count)) - { - logger("is_zero(spender_count)"); - return; - } - - // ...260 - if (spender_link.is_terminal()) - { - logger("spender_link.is_terminal()"); - return; - } - - const auto spender_link1 = query_.to_tx(hash_spender); - if (spender_link != spender_link1) - { - logger("spender_link != spender_link1"); - ////return; - } - - database::tx_link spent_link{}; - const auto hash_spent = system::base16_hash("85f65b57b88b74fd945a66a6ba392a5f3c8a7c0f78c8397228dece885d788841"); - for (auto position = zero; !cancel_ && position < txs.size(); ++position) - { - const auto temp = txs.at(position); - if (query_.get_tx_key(temp) == hash_spent) - { - spent_link = temp; - break; - } - } - - auto spent = store_.tx.it(hash_spent); - if (spent.self().is_terminal()) - return; - - // ...255, 254 - size_t spent_count{}; - do - { - const auto bar = spent.self(); - ++spent_count; - } while (spent.advance()); - - if (is_zero(spent_count)) - { - logger("is_zero(spent_count)"); - return; - } - - // ...254 (not ...255) - if (spent_link.is_terminal()) - { - logger("spent_link.is_terminal()"); - return; - } - - const auto spent_link1 = query_.to_tx(hash_spent); - if (spent_link != spent_link1) - { - logger("spent_link != spent_link1"); - ////return; - } - - const auto tx = query_.to_tx(hash_spender); - if (tx.is_terminal()) - { - logger("!tx"); - return; - } - - if (tx != spender_link) - { - logger("tx != spender_link"); - return; - } - - if (spender_link <= spent_link) - { - logger("spender_link <= spent_link"); - return; - } - - // ...254 - const auto header1 = query_.to_block(spender_link); - if (header1.is_terminal()) - { - logger("header1.is_terminal()"); - return; - } - - // ...255 (the latter instance is not confirmed) - const auto header11 = query_.to_block(add1(spender_link)); - if (!header11.is_terminal()) - { - logger("!header11.is_terminal()"); - return; - } - - // ...260 - const auto header2 = query_.to_block(spent_link); - if (header2.is_terminal()) - { - logger("auto.is_terminal()"); - return; - } - - // ...261 (the latter instance is not confirmed) - const auto header22 = query_.to_block(add1(spent_link)); - if (!header22.is_terminal()) - { - logger("!header22.is_terminal()"); - return; - } - - if (header1 != header2) - { - logger("header1 != header2"); - return; - } - - if (header1 != block) - { - logger("header1 != block"); - return; - } - - const auto ec = query_.block_confirmable(query_.to_confirmed(height)); - logger(format("Confirm [%1%] test (%2%).") % height % ec.message()); -} - -void executor::read_test() const -{ - const auto bk_link = query_.to_candidate(804'001_size); - const auto block = query_.get_block(bk_link); - if (!block) - { - logger("!query_.get_block(link)"); - return; - } - - ////const auto tx = query_.get_transaction({ 980'984'671_u32 }); - ////if (!tx) - ////{ - //// logger("!query_.get_transaction(tx_link)"); - //// return; - ////} - //// - ////chain::context ctx{}; - ////if (!query_.get_context(ctx, bk_link)) - ////{ - //// logger("!query_.get_context(ctx, bk_link)"); - //// return; - ////} - //// - ////if (!query_.populate_with_metadata(*tx)) - ////{ - //// logger("!query_.populate_with_metadata(*tx)"); - //// return; - ////} - //// - ////if (const auto ec = tx->confirm(ctx)) - //// logger(format("Error confirming tx [980'984'671] %1%") % ec.message()); - //// - ////// Does not compute spent metadata, assumes coinbase spent and others not. - ////if (!query_.populate_with_metadata(*block)) - ////{ - //// logger("!query_.populate_with_metadata(*block)"); - //// return; - ////} - //// - ////const auto& txs = *block->transactions_ptr(); - ////if (txs.empty()) - ////{ - //// logger("txs.empty()"); - //// return; - ////} - //// - ////for (auto index = one; index < txs.size(); ++index) - //// if (const auto ec = txs.at(index)->confirm(ctx)) - //// logger(format("Error confirming tx [%1%] %2%") % index % ec.message()); - //// - ////logger("Confirm test 1 complete."); - - const auto ec = query_.block_confirmable(bk_link); - logger(format("Confirm test 2 complete (%1%).") % ec.message()); -} - -void executor::read_test() const -{ - using namespace database; - constexpr auto frequency = 100'000u; - const auto start = fine_clock::now(); - auto tx = 664'400'000_size; - - // Read all data except genesis (ie. for validation). - while (!cancel_ && (++tx < query_.tx_records())) - { - const tx_link link{ - system::possible_narrow_cast(tx) }; - - ////const auto ptr = query_.get_header(link); - ////if (!ptr) - ////{ - //// logger("Failure: get_header"); - //// break; - ////} - ////else if (is_zero(ptr->bits())) - ////{ - //// logger("Failure: zero bits"); - //// break; - ////} - - ////const auto txs = query_.to_transactions(link); - ////if (txs.empty()) - ////{ - //// logger("Failure: to_txs"); - //// break; - ////} - - const auto ptr = query_.get_transaction(link); - if (!ptr) - { - logger("Failure: get_transaction"); - break; - } - else if (!ptr->is_valid()) - { - logger("Failure: is_valid"); - break; - } - - if (is_zero(tx % frequency)) - logger(format("get_transaction" BN_READ_ROW) % tx % - duration_cast(fine_clock::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("get_transaction" BN_READ_ROW) % tx % span.count()); -} - -void executor::read_test() const -{ - constexpr auto hash492224 = base16_hash( - "0000000000000000003277b639e56dffe2b4e60d18aeedb1fe8b7e4256b2a526"); - - logger("HIT TO START"); - std::string line{}; - std::getline(input_, line); - const auto start = fine_clock::now(); - - for (size_t height = 492'224; (height <= 492'224) && !cancel_; ++height) - { - // 2s 0s - const auto link = query_.to_header(hash492224); - if (link.is_terminal()) - { - logger("to_header"); - return; - } - - ////const auto link = query_.to_confirmed(height); - ////if (link.is_terminal()) - ////{ - //// logger("to_confirmed"); - //// return; - ////} - - // 109s 111s - const auto block = query_.get_block(link); - if (!block || !block->is_valid() || block->hash() != hash492224) - { - logger("get_block"); - return; - } - - // 125s 125s - code ec{}; - if ((ec = block->check())) - { - logger(format("Block [%1%] check1: %2%") % height % ec.message()); - return; - } - - // 117s 122s - if (chain::checkpoint::is_conflict( - metadata_.configured.bitcoin.checkpoints, block->hash(), height)) - { - logger(format("Block [%1%] checkpoint conflict") % height); - return; - } - - ////// ???? 125s/128s - ////block->populate(); - - // 191s 215s/212s/208s [independent] - // ???? 228s/219s/200s [combined] - if (!query_.populate(*block)) - { - logger("populate"); - return; - } - - // 182s - database::context ctx{}; - if (!query_.get_context(ctx, link) || ctx.height != height) - { - logger("get_context"); - return; - } - - // Fabricate chain_state context from store context. - chain::context state{}; - state.flags = ctx.flags; - state.height = ctx.height; - state.median_time_past = ctx.mtp; - state.timestamp = block->header().timestamp(); - - // split from accept. - if ((ec = block->check(state))) - { - logger(format("Block [%1%] check2: %2%") % height % ec.message()); - return; - } - - // 199s - const auto& coin = metadata_.configured.bitcoin; - if ((ec = block->accept(state, coin.subsidy_interval_blocks, - coin.initial_subsidy()))) - { - logger(format("Block [%1%] accept: %2%") % height % ec.message()); - return; - } - - // 1410s - if ((ec = block->connect(state))) - { - logger(format("Block [%1%] connect: %2%") % height % ec.message()); - return; - } - - ////for (size_t index = one; index < block->transactions_ptr()->size(); ++index) - ////{ - //// constexpr size_t index = 1933; - //// const auto& tx = *block->transactions_ptr()->at(index); - //// if ((ec = tx.connect(state))) - //// { - //// logger(format("Tx (%1%) [%2%] %3%") - //// % index - //// % encode_hash(tx.hash(false)) - //// % ec.message()); - //// } - ////} - - // +10s for all. - logger(format("block:%1%") % height); - ////logger(format("block:%1% flags:%2% mtp:%3%") % - //// ctx.height % ctx.flags % ctx.mtp); - } - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("STOP (%1% secs)") % span.count()); -} - -// TODO: create a block/tx dumper. -void executor::read_test() const -{ - constexpr auto hash523354 = base16_hash( - "0000000000000000002e0e763e60bde40c58aa23f295ef1919b352f3303e06a6"); - - const auto start = logger::now(); - const auto link = query_.to_header(hash523354); - if (link.is_terminal()) - { - logger("link.is_terminal()"); - return; - } - - const auto block = query_.get_block(link); - if (!block) - { - logger("!block"); - return; - } - - if (!block->is_valid()) - { - logger("!block->is_valid()"); - return; - } - - database::context ctx{}; - if (!query_.get_context(ctx, link)) - { - logger("!query_.get_context(ctx, link)"); - return; - } - - // flags:147455 height:523354 mtp:1526711964 - logger(format("flags:%1% height:%2% mtp:%3%") % - ctx.flags % ctx.height % ctx.mtp); - - // minimum_block_version and work_required are only for header validate. - chain::context state{}; - state.flags = ctx.flags; - state.height = ctx.height; - state.median_time_past = ctx.mtp; - state.timestamp = block->header().timestamp(); - state.minimum_block_version = 0; - state.work_required = 0; - if (!query_.populate(*block)) - { - logger("!query_.populate(*block)"); - return; - } - - code ec{}; - if ((ec = block->check())) - { - logger(format("Block check: %1%") % ec.message()); - return; - } - - const auto& coin = metadata_.configured.bitcoin; - if ((ec = block->accept(state, coin.subsidy_interval_blocks, - coin.initial_subsidy()))) - { - logger(format("Block accept: %1%") % ec.message()); - return; - } - - if ((ec = block->connect(state))) - { - logger(format("Block connect: %1%") % ec.message()); - return; - } - - const auto span = duration_cast(logger::now() - start); - logger(format("Validated block 523354 in %1% msec.") % span.count()); -} - -#endif // UNDEFINED - -// arbitrary testing (non-const). -void executor::write_test(bool) -{ - logger("No write test implemented."); -} - -#if defined(UNDEFINED) - -void executor::write_test() -{ - code ec{}; - size_t count{}; - const auto start = fine_clock::now(); - - const auto fork = query_.get_fork(); - const auto top_associated = query_.get_top_associated_from(fork); - - for (auto height = fork; !cancel_ && !ec && height <= top_associated; - ++height, ++count) - { - const auto block = query_.to_candidate(height); - if (!query_.set_strong(block)) - { - logger(format("set_strong [%1%] fault.") % height); - return; - } - - ////if (ec = query_.block_confirmable(block)) - ////{ - //// logger(format("block_confirmable [%1%] fault (%2%).") % height % ec.message()); - //// return; - ////} - //// - ////if (!query_.set_block_confirmable(block, uint64_t{})) - ////{ - //// logger(format("set_block_confirmable [%1%] fault.") % height); - //// return; - ////} - - if (!query_.push_confirmed(block)) - { - logger(format("push_confirmed [%1%] fault.") % height); - return; - } - - if (is_zero(height % 1000_size)) - logger(format("write_test [%1%].") % height); - } - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("Set confirmation of %1% blocks in %2% secs.") % count % - span.count()); -} - -void executor::write_test() -{ - using namespace database; - constexpr auto frequency = 10'000; - const auto start = fine_clock::now(); - logger(BN_OPERATION_INTERRUPT); - - auto height = query_.get_top_candidate(); - while (!cancel_ && (++height < query_.header_records())) - { - // Assumes height is header link. - const header_link link{ possible_narrow_cast(height) }; - - if (!query_.push_confirmed(link)) - { - logger("!query_.push_confirmed(link)"); - return; - } - - if (!query_.push_candidate(link)) - { - logger("!query_.push_candidate(link)"); - return; - } - - if (is_zero(height % frequency)) - logger(format("block" BN_WRITE_ROW) % height % - duration_cast(fine_clock::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("block" BN_WRITE_ROW) % height % span.count()); -} - -void executor::write_test() -{ - using namespace database; - ////constexpr uint64_t fees = 99; - constexpr auto frequency = 10'000; - const auto start = fine_clock::now(); - code ec{}; - - logger(BN_OPERATION_INTERRUPT); - - auto height = zero;//// query_.get_top_confirmed(); - const auto records = query_.header_records(); - while (!cancel_ && (++height < records)) - { - // Assumes height is header link. - const header_link link{ possible_narrow_cast(height) }; - - if (!query_.set_strong(link)) - { - // total sequential chain cost: 18.7 min (now 6.6). - logger("Failure: set_strong"); - break; - } - else if ((ec = query_.block_confirmable(link))) - { - // must set_strong before each (no push, verifies non-use). - logger(format("Failure: block_confirmed, %1%") % ec.message()); - break; - } - ////if (!query_.set_txs_connected(link)) - ////{ - //// // total sequential chain cost: 21 min. - //// logger("Failure: set_txs_connected"); - //// break; - ////} - ////if (!query_.set_block_confirmable(link, fees)) - ////{ - //// // total chain cost: 1 sec. - //// logger("Failure: set_block_confirmable"); - //// break; - //// break; - ////} - ////else if (!query_.push_candidate(link)) - ////{ - //// // total chain cost: 1 sec. - //// logger("Failure: push_candidate"); - //// break; - ////} - ////else if (!query_.push_confirmed(link)) - ////{ - //// // total chain cost: 1 sec. - //// logger("Failure: push_confirmed"); - //// break; - ////} - - if (is_zero(height % frequency)) - logger(format("block" BN_WRITE_ROW) % height % - duration_cast(fine_clock::now() - start).count()); - } - - if (cancel_) - logger(BN_OPERATION_CANCELED); - - const auto span = duration_cast(fine_clock::now() - start); - logger(format("block" BN_WRITE_ROW) % height % span.count()); -} - -void executor::write_test() -{ - constexpr auto hash251684 = base16_hash( - "00000000000000720e4c59ad28a8b61f38015808e92465e53111e3463aed80de"); - const auto link = query_.to_header(hash251684); - if (link.is_terminal()) - { - logger("link.is_terminal()"); - return; - } - - if (query_.confirmed_records() != 251684u) - { - logger("!query_.confirmed_records() != 251684u"); - return; - } - - if (!query_.push_confirmed(link)) - { - logger("!query_.push_confirmed(link)"); - return; - } - - if (query_.confirmed_records() != 251685u) - { - logger("!query_.confirmed_records() != 251685u"); - return; - } - - logger("Successfully confirmed block 251684."); -} - -#endif // UNDEFINED - -// Store functions. -// ---------------------------------------------------------------------------- - -bool executor::check_store_path(bool create) const -{ - const auto& configuration = metadata_.configured.file; - if (configuration.empty()) - { - logger(BN_USING_DEFAULT_CONFIG); - } - else - { - logger(format(BN_USING_CONFIG_FILE) % configuration); - } - - const auto& store = metadata_.configured.database.path; - if (create) - { - logger(format(BN_INITIALIZING_CHAIN) % store); - if (!database::file::create_directory(store)) - { - logger(format(BN_INITCHAIN_EXISTS) % store); - return false; - } - } - else - { - if (!database::file::is_directory(store)) - { - logger(format(BN_UNINITIALIZED_DATABASE) % store); - return false; - } - } - - return true; -} - -bool executor::create_store(bool details) -{ - logger(BN_INITCHAIN_CREATING); - const auto start = logger::now(); - if (const auto ec = store_.create([&](auto event_, auto table) - { - if (details) - logger(format(BN_CREATE) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_INITCHAIN_DATABASE_CREATE_FAILURE) % ec.message()); - return false; - } - - // Create and confirm genesis block (store invalid without it). - logger(BN_INITCHAIN_DATABASE_INITIALIZE); - if (!query_.initialize(metadata_.configured.bitcoin.genesis_block)) - { - logger(BN_INITCHAIN_DATABASE_INITIALIZE_FAILURE); - close_store(details); - return false; - } - - const auto span = duration_cast(logger::now() - start); - logger(format(BN_INITCHAIN_CREATED) % span.count()); - return true; -} - -// not timed or announced (generally fast) -code executor::open_store_coded(bool details) -{ - ////logger(BN_DATABASE_STARTING); - if (const auto ec = store_.open([&](auto event_, auto table) - { - if (details) - logger(format(BN_OPEN) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_DATABASE_START_FAIL) % ec.message()); - return ec; - } - - logger(BN_DATABASE_STARTED); - return error::success; -} - -bool executor::open_store(bool details) -{ - return !open_store_coded(details); -} - -bool executor::close_store(bool details) -{ - logger(BN_DATABASE_STOPPING); - const auto start = logger::now(); - if (const auto ec = store_.close([&](auto event_, auto table) - { - if (details) - logger(format(BN_CLOSE) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_DATABASE_STOP_FAIL) % ec.message()); - return false; - } - - const auto span = duration_cast(logger::now() - start); - logger(format(BN_DATABASE_TIMED_STOP) % span.count()); - return true; -} - -bool executor::reload_store(bool details) -{ - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return false; - } - - if (const auto ec = store_.get_fault()) - { - logger(format(BN_RELOAD_INVALID) % ec.message()); - return false; - } - - logger(BN_NODE_RELOAD_STARTED); - const auto start = logger::now(); - if (const auto ec = node_->reload([&](auto event_, auto table) - { - if (details) - logger(format(BN_RELOAD) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_NODE_RELOAD_FAIL) % ec.message()); - return false; - }; - - node_->resume(); - const auto span = duration_cast(logger::now() - start); - logger(format(BN_NODE_RELOAD_COMPLETE) % span.count()); - return true; -} - -bool executor::restore_store(bool details) -{ - logger(BN_RESTORING_CHAIN); - const auto start = logger::now(); - if (const auto ec = store_.restore([&](auto event_, auto table) - { - if (details) - logger(format(BN_RESTORE) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - if (ec == database::error::flush_lock) - logger(BN_RESTORE_MISSING_FLUSH_LOCK); - else - logger(format(BN_RESTORE_FAILURE) % ec.message()); - - return false; - } - - const auto span = duration_cast(logger::now() - start); - logger(format(BN_RESTORE_COMPLETE) % span.count()); - return true; -} - -bool executor::hot_backup_store(bool details) -{ - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return false; - } - - if (const auto ec = store_.get_fault()) - { - logger(format(BN_SNAPSHOT_INVALID) % ec.message()); - return false; - } - - logger(BN_NODE_BACKUP_STARTED); - const auto start = logger::now(); - if (const auto ec = node_->snapshot([&](auto event_, auto table) - { - if (details) - logger(format(BN_BACKUP) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_NODE_BACKUP_FAIL) % ec.message()); - return false; - } - - node_->resume(); - const auto span = duration_cast(logger::now() - start); - logger(format(BN_NODE_BACKUP_COMPLETE) % span.count()); - return true; -} - -bool executor::cold_backup_store(bool details) -{ - logger(BN_NODE_BACKUP_STARTED); - const auto start = logger::now(); - if (const auto ec = store_.snapshot([&](auto event_, auto table) - { - if (details) - logger(format(BN_BACKUP) % - full_node::store::events.at(event_) % - full_node::store::tables.at(table)); - })) - { - logger(format(BN_NODE_BACKUP_FAIL) % ec.message()); - return false; - } - - const auto span = duration_cast(logger::now() - start); - logger(format(BN_NODE_BACKUP_COMPLETE) % span.count()); - return true; -} - -// Command line options. -// ---------------------------------------------------------------------------- - -// --[h]elp -bool executor::do_help() -{ - log_.stop(); - printer help(metadata_.load_options(), name_, BN_INFORMATION_MESSAGE); - help.initialize(); - help.commandline(output_); - return true; -} - -// --[d]hardware -bool executor::do_hardware() -{ - // The "try" functions are safe for instructions not compiled in. - - log_.stop(); - logger("Intrinsics..."); - logger(format("arm..... platform:%1%.") % with_arm); - logger(format("intel... platform:%1%.") % with_xcpu); - logger(format("avx512.. platform:%1% compiled:%2%.") % system::try_avx512() % with_avx512); - logger(format("avx2.... platform:%1% compiled:%2%.") % system::try_avx2() % with_avx2); - logger(format("sse41... platform:%1% compiled:%2%.") % system::try_sse41() % with_sse41); - logger(format("shani... platform:%1% compiled:%2%.") % system::try_shani() % with_shani); - logger(format("neon.... platform:%1% compiled:%2%.") % system::try_neon() % with_neon); - return true; -} - -// --[s]ettings -bool executor::do_settings() -{ - log_.stop(); - printer print(metadata_.load_settings(), name_, BN_SETTINGS_MESSAGE); - print.initialize(); - print.settings(output_); - return true; -} - -// --[v]ersion -bool executor::do_version() -{ - log_.stop(); - dump_version(); - return true; -} - -// --[n]ewstore -bool executor::do_new_store() -{ - log_.stop(); - if (!check_store_path(true) || - !create_store(true)) - return false; - - // Records and sizes reflect genesis block only. - dump_body_sizes(); - dump_records(); - dump_buckets(); - - if (!close_store(true)) - return false; - - logger(BN_INITCHAIN_COMPLETE); - return true; -} - -// --[b]ackup -bool executor::do_backup() -{ - log_.stop(); - return check_store_path() - && open_store() - && cold_backup_store(true) - && close_store(); -} - -// --[r]estore -bool executor::do_restore() -{ - log_.stop(); - return check_store_path() - && restore_store(true) - && close_store(); -} - -// --[f]lags -bool executor::do_flags() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - scan_flags(); - return close_store(); -} - -// --[i]nformation -bool executor::do_information() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - measure_size(); - return close_store(); -} - -// --[a]slabs -bool executor::do_slabs() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - scan_slabs(); - return close_store(); -} - -// --[k]buckets -bool executor::do_buckets() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - scan_buckets(); - return close_store(); -} - -// --[l]collisions -bool executor::do_collisions() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - scan_collisions(); - return close_store(); -} - -// --[t]read -bool executor::do_read() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - read_test(true); - return close_store(); -} - -// --[w]rite -bool executor::do_write() -{ - log_.stop(); - if (!check_store_path() || - !open_store()) - return false; - - write_test(true); - return close_store(); -} - -// Runtime options. -// ---------------------------------------------------------------------------- - -// [b]ackup -void executor::do_hot_backup() -{ - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return; - } - - hot_backup_store(true); -} - -// [c]lose -void executor::do_close() -{ - logger("CONSOLE: Close"); - stop(error::success); -} - -// [e]rrors -void executor::do_report_condition() const -{ - store_.report([&](const auto& ec, auto table) - { - logger(format(BN_CONDITION) % full_node::store::tables.at(table) % - ec.message()); - }); - - if (query_.is_full()) - logger(format(BN_RELOAD_SPACE) % query_.get_space()); -} - -// [h]old -void executor::do_suspend() -{ - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return; - } - - node_->suspend(error::suspended_service); -} - -// [g]o -void executor::do_resume() -{ - if (query_.is_full()) - { - logger(BN_NODE_DISK_FULL); - return; - } - - if (query_.is_fault()) - { - logger(BN_NODE_UNRECOVERABLE); - return; - } - - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return; - } - - node_->resume(); -} - -// [i]nfo -void executor::do_info() const -{ - dump_body_sizes(); - dump_records(); - dump_buckets(); - dump_collisions(); - ////dump_progress(); -} - -// [m]enu -void executor::do_menu() const -{ - for (const auto& toggle: toggles_menu_) - logger(format("Toggle: %1%") % toggle.second); - - for (const auto& option: options_menu_) - logger(format("Option: %1%") % option.second); -} +namespace libbitcoin { +namespace node { -// [t]est -void executor::do_test() const -{ - read_test(false); -} +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; -// [w]ork -void executor::do_report_work() -{ - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return; - } +// non-const member static (global for blocking interrupt handling). +std::promise executor::stopping_{}; - logger(format(BN_NODE_REPORT_WORK) % sequence_); - node_->notify(error::success, chase::report, sequence_++); -} +// non-const member static (global for non-blocking interrupt handling). +std::atomic_bool executor::cancel_{}; -// [z]eroize -void executor::do_reload_store() -{ - // Use do_resume command to restart connections after resetting here. - if (query_.is_full()) +executor::executor(parser& metadata, std::istream& input, std::ostream& output, + std::ostream&) + : metadata_(metadata), + store_(metadata.configured.database), + query_(store_), + input_(input), + output_(output), + toggle_ { - if (!node_) - { - logger(BN_NODE_UNAVAILABLE); - return; - } - - reload_store(true); - return; + metadata.configured.log.application, + metadata.configured.log.news, + metadata.configured.log.session, + metadata.configured.log.protocol, + metadata.configured.log.proxy, + metadata.configured.log.remote, + metadata.configured.log.fault, + metadata.configured.log.quitting, + metadata.configured.log.objects, + metadata.configured.log.verbose } - - // Any table with any error code. - logger(query_.is_fault() ? BN_NODE_UNRECOVERABLE : BN_NODE_OK); -} - -// Command line command selection. -// ---------------------------------------------------------------------------- - -bool executor::dispatch() { - const auto& config = metadata_.configured; - - if (config.help) - return do_help(); - - // Order below matches help output (alphabetical), so that first option is - // executed in the case where multiple options are parsed. - - if (config.slabs) - return do_slabs(); - - if (config.backup) - return do_backup(); - - if (config.hardware) - return do_hardware(); - - if (config.flags) - return do_flags(); - - if (config.newstore) - return do_new_store(); - - if (config.buckets) - return do_buckets(); - - if (config.collisions) - return do_collisions(); - - if (config.information) - return do_information(); - - if (config.test) - return do_read(); - - if (config.settings) - return do_settings(); - - if (config.version) - return do_version(); - - if (config.write) - return do_write(); - - if (config.restore) - return do_restore(); - - return do_run(); + initialize_stop(); } -// Run. +// Stop signal. // ---------------------------------------------------------------------------- -// TODO: verify construction failure handled. -executor::rotator_t executor::create_log_sink() const -{ - return - { - // Standard file names, within the [node].path directory. - metadata_.configured.log.log_file1(), - metadata_.configured.log.log_file2(), - to_half(metadata_.configured.log.maximum_size) - }; -} - -// TODO: throws, handle failure. -system::ofstream executor::create_event_sink() const -{ - // Standard file name, within the [node].path directory. - return { metadata_.configured.log.events_file() }; -} - -void executor::subscribe_log(std::ostream& sink) -{ - log_.subscribe_messages([&](const code& ec, uint8_t level, time_t time, - const std::string& message) - { - if (level >= toggle_.size()) - { - sink << "Invalid log [" << serialize(level) << "] : " << message; - output_ << "Invalid log [" << serialize(level) << "] : " << message; - output_.flush(); - return true; - } - - // Write only selected logs. - if (!ec && !toggle_.at(level)) - return true; - - const auto prefix = format_zulu_time(time) + "." + - serialize(level) + " "; - - if (ec) - { - sink << prefix << message << std::endl; - output_ << prefix << message << std::endl; - sink << prefix << BN_NODE_FOOTER << std::endl; - output_ << prefix << BN_NODE_FOOTER << std::endl; - output_ << prefix << BN_NODE_TERMINATE << std::endl; - stopped_.set_value(ec); - return false; - } - else - { - sink << prefix << message; - output_ << prefix << message; - output_.flush(); - return true; - } - }); -} - -void executor::subscribe_events(std::ostream& sink) -{ - log_.subscribe_events([&sink, start = logger::now()](const code& ec, - uint8_t event_, uint64_t value, const logger::time& point) - { - if (ec) return false; - const auto time = duration_cast(point - start).count(); - sink << fired_.at(event_) << " " << value << " " << time << std::endl; - return true; - }); -} - -void executor::subscribe_connect() +// Capture . +void executor::initialize_stop() { - node_->subscribe_connect([&](const code&, const channel::ptr&) - { - log_.write(levels::verbose) << - "{in:" << node_->inbound_channel_count() << "}" - "{ch:" << node_->channel_count() << "}" - "{rv:" << node_->reserved_count() << "}" - "{nc:" << node_->nonces_count() << "}" - "{ad:" << node_->address_count() << "}" - "{ss:" << node_->stop_subscriber_count() << "}" - "{cs:" << node_->connect_subscriber_count() << "}." - << std::endl; - - return true; - }, - [&](const code&, uintptr_t) - { - // By not handling it is possible stop could fire before complete. - // But the handler is not required for termination, so this is ok. - // The error code in the handler can be used to differentiate. - }); + std::signal(SIGINT, handle_stop); + std::signal(SIGTERM, handle_stop); } -void executor::subscribe_close() +void executor::handle_stop(int) { - node_->subscribe_close([&](const code&) - { - log_.write(levels::verbose) << - "{in:" << node_->inbound_channel_count() << "}" - "{ch:" << node_->channel_count() << "}" - "{rv:" << node_->reserved_count() << "}" - "{nc:" << node_->nonces_count() << "}" - "{ad:" << node_->address_count() << "}" - "{ss:" << node_->stop_subscriber_count() << "}" - "{cs:" << node_->connect_subscriber_count() << "}." - << std::endl; - - return false; - }, - [&](const code&, size_t) - { - // By not handling it is possible stop could fire before complete. - // But the handler is not required for termination, so this is ok. - // The error code in the handler can be used to differentiate. - }); + initialize_stop(); + stop(error::success); } -// Runtime menu selection. -void executor::subscribe_capture() +// Manage the race between console stop and server stop. +void executor::stop(const code& ec) { - // This is not on a network thread, so the node may call close() while this - // is running a backup (for example), resulting in a try_lock warning loop. - capture_.subscribe([&](const code& ec, const std::string& line) - { - // The only case in which false may be returned. - if (ec == network::error::service_stopped) - { - set_console_echo(); - return false; - } - - const auto token = trim_copy(line); - - // -c emits empty token on Win32. - if (token.empty()) - return true; - - if (toggles_.contains(token)) - { - const auto toggle = toggles_.at(token); - if (defined_.at(toggle)) - { - toggle_.at(toggle) = !toggle_.at(toggle); - logger(format("CONSOLE: toggle %1% logging (%2%).") % - toggles_menu_.at(toggle) % (toggle_.at(toggle) ? "+" : "-")); - } - else - { - logger(format("CONSOLE: %1% logging is not compiled.") % - toggles_menu_.at(toggle)); - } - - return true; - } - - if (options_.contains(token)) - { - switch (options_.at(token)) - { - case menu::backup: - { - do_hot_backup(); - return true; - } - case menu::close: - { - do_close(); - return true; - } - case menu::errors: - { - do_report_condition(); - return true; - } - case menu::go: - { - do_resume(); - return true; - } - case menu::hold: - { - do_suspend(); - return true; - } - case menu::info: - { - do_info(); - return true; - } - case menu::menu_: - { - do_menu(); - return true; - } - case menu::test: - { - do_test(); - return true; - } - case menu::work: - { - do_report_work(); - return true; - } - case menu::zeroize: - { - do_reload_store(); - return true; - } - default: - { - logger("CONSOLE: Unexpected option."); - return true; - } - } - } - - logger("CONSOLE: '" + line + "'"); - return true; - }, - [&](const code& ec) + static std::once_flag stop_mutex{}; + std::call_once(stop_mutex, [&]() { - // subscription completion handler. - if (!ec) - unset_console_echo(); + cancel_.store(true); + stopping_.set_value(ec); }); } -// ---------------------------------------------------------------------------- - -bool executor::do_run() -{ - if (!metadata_.configured.log.path.empty()) - database::file::create_directory(metadata_.configured.log.path); - - // Hold sinks in scope for the length of the run. - auto log = create_log_sink(); - auto events = create_event_sink(); - if (!log || !events) - { - logger(BN_LOG_INITIALIZE_FAILURE); - return false; - } - - subscribe_log(log); - subscribe_events(events); - subscribe_capture(); - logger(BN_LOG_HEADER); - - if (check_store_path()) - { - auto ec = open_store_coded(true); - if (ec == database::error::flush_lock) - { - ec = error::success; - if (!restore_store(true)) - ec = database::error::integrity; - } - - if (ec) - { - stopper(BN_NODE_STOPPED); - return false; - } - } - else if (!check_store_path(true) || !create_store(true)) - { - stopper(BN_NODE_STOPPED); - return false; - } - - dump_body_sizes(); - dump_records(); - dump_buckets(); - ////logger(BN_MEASURE_PROGRESS_START); - ////dump_progress(); - - // Stopped by stopper. - capture_.start(); - dump_version(); - dump_options(); - - // Create node. - metadata_.configured.network.initialize(); - node_ = std::make_shared(query_, metadata_.configured, log_); - - // Subscribe node. - subscribe_connect(); - subscribe_close(); - - // Start network. - logger(BN_NETWORK_STARTING); - node_->start(std::bind(&executor::handle_started, this, _1)); - - // Wait on signal to stop node (). - stopping_.get_future().wait(); - toggle_.at(levels::protocol) = false; - logger(BN_NETWORK_STOPPING); - - // Stop network (if not already stopped by self). - node_->close(); - - // Sizes and records change, buckets don't. - dump_body_sizes(); - dump_records(); - ////logger(BN_MEASURE_PROGRESS_START); - ////dump_progress(); - - if (!close_store(true)) - { - stopper(BN_NODE_STOPPED); - return false; - } - - stopper(BN_NODE_STOPPED); - return true; -} - +// Event handlers. // ---------------------------------------------------------------------------- void executor::handle_started(const code& ec) @@ -2693,31 +155,5 @@ bool executor::handle_stopped(const code& ec) return false; } -// Stop signal. -// ---------------------------------------------------------------------------- - -void executor::initialize_stop() NOEXCEPT -{ - std::signal(SIGINT, handle_stop); - std::signal(SIGTERM, handle_stop); -} - -void executor::handle_stop(int) -{ - initialize_stop(); - stop(error::success); -} - -// Manage the race between console stop and server stop. -void executor::stop(const code& ec) -{ - static std::once_flag stop_mutex; - std::call_once(stop_mutex, [&]() - { - cancel_.store(true); - stopping_.set_value(ec); - }); -} - } // namespace node } // namespace libbitcoin diff --git a/console/executor.hpp b/console/executor.hpp index 80c7fbb78..11d1feb78 100644 --- a/console/executor.hpp +++ b/console/executor.hpp @@ -26,6 +26,9 @@ #include #include +// This class is just an ad-hoc user interface wrapper on the node. +// It will be factored and cleaned up for final release. + namespace libbitcoin { namespace node { @@ -37,59 +40,64 @@ class executor executor(parser& metadata, std::istream&, std::ostream& output, std::ostream& error); - /// Invoke the menu command indicated by the metadata. bool dispatch(); private: - enum menu : uint8_t + void logger(const auto& message) const + { + if (log_.stopped()) + output_ << message << std::endl; + else + log_.write(network::levels::application) << message << std::endl; + } + + void stopper(const auto& message) { - backup, - close, - errors, - go, - hold, - info, - menu_, - test, - work, - zeroize - }; - - using rotator_t = database::file::stream::out::rotator; - - void logger(const auto& message) const; - void stopper(const auto& message); - - static void initialize_stop() NOEXCEPT; + capture_.stop(); + log_.stop(message, network::levels::application); + stopped_.get_future().wait(); + } + + // Stop signal. + static void initialize_stop(); static void stop(const system::code& ec); static void handle_stop(int code); + // Event handlers. void handle_started(const system::code& ec); void handle_subscribed(const system::code& ec, size_t key); void handle_running(const system::code& ec); bool handle_stopped(const system::code& ec); - // Store measures. + // Store dumps. + void dump_version() const; + void dump_hardware() const; + void dump_options() const; void dump_body_sizes() const; void dump_records() const; void dump_buckets() const; void dump_progress() const; void dump_collisions() const; - void dump_options() const; - void dump_version() const; + void dump_sizes() const; // Store functions. - code open_store_coded(bool details=false); + bool check_store_path(bool create=false) const; + bool create_store(bool details = false); bool open_store(bool details=false); + code open_store_coded(bool details=false); bool close_store(bool details=false); bool reload_store(bool details=false); - bool create_store(bool details=false); bool restore_store(bool details=false); bool hot_backup_store(bool details=false); bool cold_backup_store(bool details=false); - bool check_store_path(bool create=false) const; - // Command line options. + // Long-running queries (scans). + void scan_flags() const; + void scan_slabs() const; + void scan_buckets() const; + void scan_collisions() const; + + // Command line (defaults to do_run). bool do_help(); bool do_version(); bool do_hardware(); @@ -104,7 +112,6 @@ class executor bool do_collisions(); bool do_read(); bool do_write(); - bool do_run(); // Runtime options. void do_hot_backup(); @@ -117,23 +124,24 @@ class executor void do_test() const; void do_info() const; void do_report_condition() const; + void subscribe_capture(); - void scan_flags() const; - void measure_size() const; - void scan_buckets() const; - void scan_collisions() const; - void scan_slabs() const; + // Built in tests. void read_test(bool dump) const; void write_test(bool dump); - rotator_t create_log_sink() const; + // Logging. + database::file::stream::out::rotator create_log_sink() const; system::ofstream create_event_sink() const; void subscribe_log(std::ostream& sink); void subscribe_events(std::ostream& sink); - void subscribe_capture(); + + // Runner. void subscribe_connect(); void subscribe_close(); + bool do_run(); + // Other user-facing values. static const std::string name_; static const std::string close_; diff --git a/console/executor_commands.cpp b/console/executor_commands.cpp new file mode 100644 index 000000000..7bdd402fd --- /dev/null +++ b/console/executor_commands.cpp @@ -0,0 +1,263 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +const std::string executor::name_{ "bn" }; + +// Command line options. +// ---------------------------------------------------------------------------- + +// --[h]elp +bool executor::do_help() +{ + log_.stop(); + printer help(metadata_.load_options(), name_, BN_INFORMATION_MESSAGE); + help.initialize(); + help.commandline(output_); + return true; +} + +// --[d]hardware +bool executor::do_hardware() +{ + log_.stop(); + dump_hardware(); + return true; +} + +// --[s]ettings +bool executor::do_settings() +{ + log_.stop(); + printer print(metadata_.load_settings(), name_, BN_SETTINGS_MESSAGE); + print.initialize(); + print.settings(output_); + return true; +} + +// --[v]ersion +bool executor::do_version() +{ + log_.stop(); + dump_version(); + return true; +} + +// --[n]ewstore +bool executor::do_new_store() +{ + log_.stop(); + if (!check_store_path(true) || + !create_store(true)) + return false; + + // Records and sizes reflect genesis block only. + dump_body_sizes(); + dump_records(); + dump_buckets(); + + if (!close_store(true)) + return false; + + logger(BN_INITCHAIN_COMPLETE); + return true; +} + +// --[b]ackup +bool executor::do_backup() +{ + log_.stop(); + return check_store_path() + && open_store() + && cold_backup_store(true) + && close_store(); +} + +// --[r]estore +bool executor::do_restore() +{ + log_.stop(); + return check_store_path() + && restore_store(true) + && close_store(); +} + +// --[f]lags +bool executor::do_flags() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + scan_flags(); + return close_store(); +} + +// --[i]nformation +bool executor::do_information() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + dump_sizes(); + return close_store(); +} + +// --[a]slabs +bool executor::do_slabs() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + scan_slabs(); + return close_store(); +} + +// --[k]buckets +bool executor::do_buckets() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + scan_buckets(); + return close_store(); +} + +// --[l]collisions +bool executor::do_collisions() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + scan_collisions(); + return close_store(); +} + +// --[t]read +bool executor::do_read() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + read_test(true); + return close_store(); +} + +// --[w]rite +bool executor::do_write() +{ + log_.stop(); + if (!check_store_path() || + !open_store()) + return false; + + write_test(true); + return close_store(); +} + +// Command line dispatch. +// ---------------------------------------------------------------------------- + +bool executor::dispatch() +{ + const auto& config = metadata_.configured; + + if (config.help) + return do_help(); + + // Order below matches help output (alphabetical), so that first option is + // executed in the case where multiple options are parsed. + + if (config.slabs) + return do_slabs(); + + if (config.backup) + return do_backup(); + + if (config.hardware) + return do_hardware(); + + if (config.flags) + return do_flags(); + + if (config.newstore) + return do_new_store(); + + if (config.buckets) + return do_buckets(); + + if (config.collisions) + return do_collisions(); + + if (config.information) + return do_information(); + + if (config.test) + return do_read(); + + if (config.settings) + return do_settings(); + + if (config.version) + return do_version(); + + if (config.write) + return do_write(); + + if (config.restore) + return do_restore(); + + return do_run(); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_dumps.cpp b/console/executor_dumps.cpp new file mode 100644 index 000000000..658459005 --- /dev/null +++ b/console/executor_dumps.cpp @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +constexpr double to_double(auto integer) +{ + return 1.0 * integer; +} + +// Store dumps. +// ---------------------------------------------------------------------------- + +// emit version information for libbitcoin libraries +void executor::dump_version() const +{ + logger(format(BN_VERSION_MESSAGE) + % LIBBITCOIN_NODE_VERSION + % LIBBITCOIN_DATABASE_VERSION + % LIBBITCOIN_NETWORK_VERSION + % LIBBITCOIN_SYSTEM_VERSION); +} + +// The "try" functions are safe for instructions not compiled in. +void executor::dump_hardware() const +{ + logger("Intrinsics..."); + logger(format("arm..... platform:%1%.") % with_arm); + logger(format("intel... platform:%1%.") % with_xcpu); + logger(format("avx512.. platform:%1% compiled:%2%.") % system::try_avx512() % with_avx512); + logger(format("avx2.... platform:%1% compiled:%2%.") % system::try_avx2() % with_avx2); + logger(format("sse41... platform:%1% compiled:%2%.") % system::try_sse41() % with_sse41); + logger(format("shani... platform:%1% compiled:%2%.") % system::try_shani() % with_shani); + logger(format("neon.... platform:%1% compiled:%2%.") % system::try_neon() % with_neon); +} + +// logging compilation and initial values. +void executor::dump_options() const +{ + logger(BN_NODE_INTERRUPT); + logger(BN_LOG_TABLE_HEADER); + logger(format("[a]pplication.. " BN_LOG_TABLE) % levels::application_defined % toggle_.at(levels::application)); + logger(format("[n]ews......... " BN_LOG_TABLE) % levels::news_defined % toggle_.at(levels::news)); + logger(format("[s]ession...... " BN_LOG_TABLE) % levels::session_defined % toggle_.at(levels::session)); + logger(format("[p]rotocol..... " BN_LOG_TABLE) % levels::protocol_defined % toggle_.at(levels::protocol)); + logger(format("[x]proxy....... " BN_LOG_TABLE) % levels::proxy_defined % toggle_.at(levels::proxy)); + logger(format("[r]emote....... " BN_LOG_TABLE) % levels::remote_defined % toggle_.at(levels::remote)); + logger(format("[f]ault........ " BN_LOG_TABLE) % levels::fault_defined % toggle_.at(levels::fault)); + logger(format("[q]uitting..... " BN_LOG_TABLE) % levels::quitting_defined % toggle_.at(levels::quitting)); + logger(format("[o]bjects...... " BN_LOG_TABLE) % levels::objects_defined % toggle_.at(levels::objects)); + logger(format("[v]erbose...... " BN_LOG_TABLE) % levels::verbose_defined % toggle_.at(levels::verbose)); +} + +void executor::dump_body_sizes() const +{ + logger(format(BN_MEASURE_SIZES) % + query_.header_body_size() % + query_.txs_body_size() % + query_.tx_body_size() % + query_.point_body_size() % + query_.input_body_size() % + query_.output_body_size() % + query_.puts_body_size() % + query_.candidate_body_size() % + query_.confirmed_body_size() % + query_.spend_body_size() % + query_.strong_tx_body_size() % + query_.validated_tx_body_size() % + query_.validated_bk_body_size() % + query_.address_body_size() % + query_.neutrino_body_size()); +} + +void executor::dump_records() const +{ + logger(format(BN_MEASURE_RECORDS) % + query_.header_records() % + query_.tx_records() % + query_.point_records() % + query_.candidate_records() % + query_.confirmed_records() % + query_.spend_records() % + query_.strong_tx_records() % + query_.address_records()); +} + +void executor::dump_buckets() const +{ + logger(format(BN_MEASURE_BUCKETS) % + query_.header_buckets() % + query_.txs_buckets() % + query_.tx_buckets() % + query_.point_buckets() % + query_.spend_buckets() % + query_.strong_tx_buckets() % + query_.validated_tx_buckets() % + query_.validated_bk_buckets() % + query_.address_buckets() % + query_.neutrino_buckets()); +} + +// txs, validated_tx, validated_bk collision rates assume 1:1 records. +void executor::dump_collisions() const +{ + logger(format(BN_MEASURE_COLLISION_RATES) % + (to_double(query_.header_records()) / query_.header_buckets()) % + (to_double(query_.header_records()) / query_.txs_buckets()) % + (to_double(query_.tx_records()) / query_.tx_buckets()) % + (to_double(query_.point_records()) / query_.point_buckets()) % + (to_double(query_.spend_records()) / query_.spend_buckets()) % + (to_double(query_.strong_tx_records()) / query_.strong_tx_buckets()) % + (to_double(query_.tx_records()) / query_.validated_tx_buckets()) % + (to_double(query_.header_records()) / query_.validated_bk_buckets()) % + (query_.address_enabled() ? (to_double(query_.address_records()) / + query_.address_buckets()) : 0) % + (query_.neutrino_enabled() ? (to_double(query_.header_records()) / + query_.neutrino_buckets()) : 0)); +} + +void executor::dump_progress() const +{ + logger(format(BN_MEASURE_PROGRESS) % + query_.get_fork() % + query_.get_top_confirmed() % + encode_hash(query_.get_header_key(query_.to_confirmed( + query_.get_top_confirmed()))) % + query_.get_top_candidate() % + encode_hash(query_.get_header_key(query_.to_candidate( + query_.get_top_candidate()))) % + query_.get_top_associated() % + (query_.get_top_candidate() - query_.get_unassociated_count()) % + query_.get_confirmed_size() % + query_.get_candidate_size()); +} + +// file and logical sizes. +void executor::dump_sizes() const +{ + dump_body_sizes(); + dump_records(); + dump_buckets(); + dump_collisions(); + + // This one can take a few seconds on cold iron. + logger(BN_MEASURE_PROGRESS_START); + dump_progress(); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_events.cpp b/console/executor_events.cpp new file mode 100644 index 000000000..2ec4a0ed8 --- /dev/null +++ b/console/executor_events.cpp @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +const std::unordered_map executor::fired_ +{ + { events::header_archived, "header_archived....." }, + { events::header_organized, "header_organized...." }, + { events::header_reorganized, "header_reorganized.." }, + + { events::block_archived, "block_archived......" }, + { events::block_buffered, "block_buffered......" }, + { events::block_validated, "block_validated....." }, + { events::block_confirmed, "block_confirmed....." }, + { events::block_unconfirmable, "block_unconfirmable." }, + { events::validate_bypassed, "validate_bypassed..." }, + { events::confirm_bypassed, "confirm_bypassed...." }, + + { events::tx_archived, "tx_archived........." }, + { events::tx_validated, "tx_validated........" }, + { events::tx_invalidated, "tx_invalidated......" }, + + { events::block_organized, "block_organized....." }, + { events::block_reorganized, "block_reorganized..." }, + + { events::template_issued, "template_issued....." }, + + { events::snapshot_span, "snapshot_span......." } +}; + +// Events. +// ---------------------------------------------------------------------------- + +// TODO: throws, handle failure. +system::ofstream executor::create_event_sink() const +{ + // Standard file name, within the [node].path directory. + return { metadata_.configured.log.events_file() }; +} + +void executor::subscribe_events(std::ostream& sink) +{ + log_.subscribe_events([&sink, start = logger::now()](const code& ec, + uint8_t event_, uint64_t value, const logger::time& point) + { + if (ec) return false; + const auto time = duration_cast(point - start).count(); + sink << fired_.at(event_) << " " << value << " " << time << std::endl; + return true; + }); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_logging.cpp b/console/executor_logging.cpp new file mode 100644 index 000000000..88f75052b --- /dev/null +++ b/console/executor_logging.cpp @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +const std::unordered_map executor::defined_ +{ + { levels::application, levels::application_defined }, + { levels::news, levels::news_defined }, + { levels::session, levels::session_defined }, + { levels::protocol, levels::protocol_defined }, + { levels::proxy, levels::proxy_defined }, + { levels::remote, levels::remote_defined }, + { levels::fault, levels::fault_defined }, + { levels::quitting, levels::quitting_defined }, + { levels::objects, levels::objects_defined }, + { levels::verbose, levels::verbose_defined }, +}; + +// Logging. +// ---------------------------------------------------------------------------- + +// TODO: verify construction failure handled. +database::file::stream::out::rotator executor::create_log_sink() const +{ + return + { + // Standard file names, within the [node].path directory. + metadata_.configured.log.log_file1(), + metadata_.configured.log.log_file2(), + to_half(metadata_.configured.log.maximum_size) + }; +} + +void executor::subscribe_log(std::ostream& sink) +{ + log_.subscribe_messages([&](const code& ec, uint8_t level, time_t time, + const std::string& message) + { + if (level >= toggle_.size()) + { + sink << "Invalid log [" << serialize(level) << "] : " << message; + output_ << "Invalid log [" << serialize(level) << "] : " << message; + output_.flush(); + return true; + } + + // Write only selected logs. + if (!ec && !toggle_.at(level)) + return true; + + const auto prefix = format_zulu_time(time) + "." + + serialize(level) + " "; + + if (ec) + { + sink << prefix << message << std::endl; + output_ << prefix << message << std::endl; + sink << prefix << BN_NODE_FOOTER << std::endl; + output_ << prefix << BN_NODE_FOOTER << std::endl; + output_ << prefix << BN_NODE_TERMINATE << std::endl; + stopped_.set_value(ec); + return false; + } + else + { + sink << prefix << message; + output_ << prefix << message; + output_.flush(); + return true; + } + }); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_options.cpp b/console/executor_options.cpp new file mode 100644 index 000000000..207d7ec28 --- /dev/null +++ b/console/executor_options.cpp @@ -0,0 +1,363 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +// local +enum menu : uint8_t +{ + backup, + close, + errors, + go, + hold, + info, + menu_, + test, + work, + zeroize +}; + +// for capture construct, defines [c]lose command key. +const std::string executor::close_{ "c" }; + +const std::unordered_map executor::options_ +{ + { "b", menu::backup }, + { "c", menu::close }, + { "e", menu::errors }, + { "g", menu::go }, + { "h", menu::hold }, + { "i", menu::info }, + { "m", menu::menu_ }, + { "t", menu::test }, + { "w", menu::work }, + { "z", menu::zeroize } +}; + +const std::unordered_map executor::options_menu_ +{ + { menu::backup, "[b]ackup the store" }, + { menu::close, "[c]lose the node" }, + { menu::errors, "[e]rrors in store" }, + { menu::go, "[g]o network communication" }, + { menu::hold, "[h]old network communication" }, + { menu::info, "[i]nfo about store" }, + { menu::menu_, "[m]enu of options and toggles" }, + { menu::test, "[t]est built-in case" }, + { menu::work, "[w]ork distribution" }, + { menu::zeroize, "[z]eroize disk full error" } +}; + +const std::unordered_map executor::toggles_ +{ + { "a", levels::application }, + { "n", levels::news }, + { "s", levels::session }, + { "p", levels::protocol }, + { "x", levels::proxy }, + { "r", levels::remote }, + { "f", levels::fault }, + { "q", levels::quitting }, + { "o", levels::objects }, + { "v", levels::verbose } +}; + +const std::unordered_map executor::toggles_menu_ +{ + { levels::application, "[a]pplication" }, + { levels::news, "[n]ews" }, + { levels::session, "[s]ession" }, + { levels::protocol, "[p]rotocol" }, + { levels::proxy, "[x]proxy" }, + { levels::remote, "[r]emote" }, + { levels::fault, "[f]ault" }, + { levels::quitting, "[q]uitting" }, + { levels::objects, "[o]bjects" }, + { levels::verbose, "[v]erbose" } +}; + +// Runtime options. +// ---------------------------------------------------------------------------- + +// [b]ackup +void executor::do_hot_backup() +{ + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return; + } + + hot_backup_store(true); +} + +// [c]lose +void executor::do_close() +{ + logger("CONSOLE: Close"); + stop(error::success); +} + +// [e]rrors +void executor::do_report_condition() const +{ + store_.report([&](const auto& ec, auto table) + { + logger(format(BN_CONDITION) % full_node::store::tables.at(table) % + ec.message()); + }); + + if (query_.is_full()) + logger(format(BN_RELOAD_SPACE) % query_.get_space()); +} + +// [h]old +void executor::do_suspend() +{ + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return; + } + + node_->suspend(error::suspended_service); +} + +// [g]o +void executor::do_resume() +{ + if (query_.is_full()) + { + logger(BN_NODE_DISK_FULL); + return; + } + + if (query_.is_fault()) + { + logger(BN_NODE_UNRECOVERABLE); + return; + } + + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return; + } + + node_->resume(); +} + +// [i]nfo +void executor::do_info() const +{ + dump_body_sizes(); + dump_records(); + dump_buckets(); + dump_collisions(); + ////dump_progress(); +} + +// [m]enu +void executor::do_menu() const +{ + for (const auto& toggle: toggles_menu_) + logger(format("Toggle: %1%") % toggle.second); + + for (const auto& option: options_menu_) + logger(format("Option: %1%") % option.second); +} + +// [t]est +void executor::do_test() const +{ + read_test(false); +} + +// [w]ork +void executor::do_report_work() +{ + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return; + } + + logger(format(BN_NODE_REPORT_WORK) % sequence_); + node_->notify(error::success, chase::report, sequence_++); +} + +// [z]eroize +void executor::do_reload_store() +{ + // Use do_resume command to restart connections after resetting here. + if (query_.is_full()) + { + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return; + } + + reload_store(true); + return; + } + + // Any table with any error code. + logger(query_.is_fault() ? BN_NODE_UNRECOVERABLE : BN_NODE_OK); +} + +// Runtime options/toggles dispatch. +// ---------------------------------------------------------------------------- + +void executor::subscribe_capture() +{ + // This is not on a network thread, so the node may call close() while this + // is running a backup (for example), resulting in a try_lock warning loop. + capture_.subscribe([&](const code& ec, const std::string& line) + { + // The only case in which false may be returned. + if (ec == network::error::service_stopped) + { + set_console_echo(); + return false; + } + + const auto token = trim_copy(line); + + // -c emits empty token on Win32. + if (token.empty()) + return true; + + // toggle log levels + if (toggles_.contains(token)) + { + const auto toggle = toggles_.at(token); + if (defined_.at(toggle)) + { + toggle_.at(toggle) = !toggle_.at(toggle); + logger(format("CONSOLE: toggle %1% logging (%2%).") % + toggles_menu_.at(toggle) % (toggle_.at(toggle) ? "+" : "-")); + } + else + { + logger(format("CONSOLE: %1% logging is not compiled.") % + toggles_menu_.at(toggle)); + } + + return true; + } + + // dispatch options + if (options_.contains(token)) + { + switch (options_.at(token)) + { + case menu::backup: + { + do_hot_backup(); + return true; + } + case menu::close: + { + do_close(); + return true; + } + case menu::errors: + { + do_report_condition(); + return true; + } + case menu::go: + { + do_resume(); + return true; + } + case menu::hold: + { + do_suspend(); + return true; + } + case menu::info: + { + do_info(); + return true; + } + case menu::menu_: + { + do_menu(); + return true; + } + case menu::test: + { + do_test(); + return true; + } + case menu::work: + { + do_report_work(); + return true; + } + case menu::zeroize: + { + do_reload_store(); + return true; + } + default: + { + logger("CONSOLE: Unexpected option."); + return true; + } + } + } + + logger("CONSOLE: '" + line + "'"); + return true; + }, + [&](const code& ec) + { + // subscription completion handler. + if (!ec) + unset_console_echo(); + }); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_runner.cpp b/console/executor_runner.cpp new file mode 100644 index 000000000..6a92edd1d --- /dev/null +++ b/console/executor_runner.cpp @@ -0,0 +1,183 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +// Runner. +// ---------------------------------------------------------------------------- + +void executor::subscribe_connect() +{ + node_->subscribe_connect([&](const code&, const channel::ptr&) + { + log_.write(levels::verbose) << + "{in:" << node_->inbound_channel_count() << "}" + "{ch:" << node_->channel_count() << "}" + "{rv:" << node_->reserved_count() << "}" + "{nc:" << node_->nonces_count() << "}" + "{ad:" << node_->address_count() << "}" + "{ss:" << node_->stop_subscriber_count() << "}" + "{cs:" << node_->connect_subscriber_count() << "}." + << std::endl; + + return true; + }, + [&](const code&, uintptr_t) + { + // By not handling it is possible stop could fire before complete. + // But the handler is not required for termination, so this is ok. + // The error code in the handler can be used to differentiate. + }); +} + +void executor::subscribe_close() +{ + node_->subscribe_close([&](const code&) + { + log_.write(levels::verbose) << + "{in:" << node_->inbound_channel_count() << "}" + "{ch:" << node_->channel_count() << "}" + "{rv:" << node_->reserved_count() << "}" + "{nc:" << node_->nonces_count() << "}" + "{ad:" << node_->address_count() << "}" + "{ss:" << node_->stop_subscriber_count() << "}" + "{cs:" << node_->connect_subscriber_count() << "}." + << std::endl; + + return false; + }, + [&](const code&, size_t) + { + // By not handling it is possible stop could fire before complete. + // But the handler is not required for termination, so this is ok. + // The error code in the handler can be used to differentiate. + }); +} + +bool executor::do_run() +{ + if (!metadata_.configured.log.path.empty()) + database::file::create_directory(metadata_.configured.log.path); + + // Hold sinks in scope for the length of the run. + auto log = create_log_sink(); + auto events = create_event_sink(); + if (!log || !events) + { + logger(BN_LOG_INITIALIZE_FAILURE); + return false; + } + + subscribe_log(log); + subscribe_events(events); + subscribe_capture(); + logger(BN_LOG_HEADER); + + if (check_store_path()) + { + auto ec = open_store_coded(true); + if (ec == database::error::flush_lock) + { + ec = error::success; + if (!restore_store(true)) + ec = database::error::integrity; + } + + if (ec) + { + stopper(BN_NODE_STOPPED); + return false; + } + } + else if (!check_store_path(true) || !create_store(true)) + { + stopper(BN_NODE_STOPPED); + return false; + } + + dump_body_sizes(); + dump_records(); + dump_buckets(); + ////logger(BN_MEASURE_PROGRESS_START); + ////dump_progress(); + + // Stopped by stopper. + capture_.start(); + dump_version(); + dump_options(); + + // Create node. + metadata_.configured.network.initialize(); + node_ = std::make_shared(query_, metadata_.configured, log_); + + // Subscribe node. + subscribe_connect(); + subscribe_close(); + + // Start network. + logger(BN_NETWORK_STARTING); + node_->start(std::bind(&executor::handle_started, this, _1)); + + // Wait on signal to stop node (). + stopping_.get_future().wait(); + toggle_.at(levels::protocol) = false; + logger(BN_NETWORK_STOPPING); + + // Stop network (if not already stopped by self). + node_->close(); + + // Sizes and records change, buckets don't. + dump_body_sizes(); + dump_records(); + ////logger(BN_MEASURE_PROGRESS_START); + ////dump_progress(); + + if (!close_store(true)) + { + stopper(BN_NODE_STOPPED); + return false; + } + + stopper(BN_NODE_STOPPED); + return true; +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_scans.cpp b/console/executor_scans.cpp new file mode 100644 index 000000000..b1874012c --- /dev/null +++ b/console/executor_scans.cpp @@ -0,0 +1,468 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +constexpr double to_double(auto integer) +{ + return 1.0 * integer; +} + +// fork flag transitions (candidate chain). +void executor::scan_flags() const +{ + const auto start = logger::now(); + constexpr auto flag_bits = to_bits(sizeof(chain::flags)); + const auto error = code{ database::error::integrity }.message(); + const auto top = query_.get_top_candidate(); + uint32_t flags{}; + + logger(BN_OPERATION_INTERRUPT); + + for (size_t height{}; !cancel_ && height <= top; ++height) + { + database::context ctx{}; + const auto link = query_.to_candidate(height); + if (!query_.get_context(ctx, link) || (ctx.height != height)) + { + logger(format("Error: %1%") % error); + return; + } + + if (ctx.flags != flags) + { + const binary prev{ flag_bits, to_big_endian(flags) }; + const binary next{ flag_bits, to_big_endian(ctx.flags) }; + logger(format("Forked from [%1%] to [%2%] at [%3%:%4%]") % prev % + next % encode_hash(query_.get_header_key(link)) % height); + flags = ctx.flags; + } + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + const auto span = duration_cast(logger::now() - start); + logger(format("Scanned %1% headers for rule forks in %2% ms.") % top % + span.count()); +} + +// input and output table slab counts. +void executor::scan_slabs() const +{ + logger(BN_MEASURE_SLABS); + logger(BN_OPERATION_INTERRUPT); + database::tx_link::integer link{}; + size_t inputs{}, outputs{}; + const auto start = logger::now(); + constexpr auto frequency = 100'000; + + // Tx (record) links are sequential and so iterable, however the terminal + // condition assumes all tx entries fully written (ok for stopped node). + // A running node cannot safely iterate over record links, but stopped can. + for (auto puts = query_.put_counts(link); to_bool(puts.first) && !cancel_; + puts = query_.put_counts(++link)) + { + inputs += puts.first; + outputs += puts.second; + if (is_zero(link % frequency)) + logger(format(BN_MEASURE_SLABS_ROW) % link % inputs % outputs); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + const auto span = duration_cast(logger::now() - start); + logger(format(BN_MEASURE_STOP) % inputs % outputs % span.count()); +} + +// hashmap bucket fill rates. +void executor::scan_buckets() const +{ + constexpr auto block_frequency = 10'000u; + constexpr auto tx_frequency = 1'000'000u; + constexpr auto put_frequency = 10'000'000u; + + logger(BN_OPERATION_INTERRUPT); + + auto filled = zero; + auto bucket = max_size_t; + auto start = logger::now(); + while (!cancel_ && (++bucket < query_.header_buckets())) + { + const auto top = query_.top_header(bucket); + if (!top.is_terminal()) + ++filled; + + if (is_zero(bucket % block_frequency)) + logger(format("header" BN_READ_ROW) % bucket % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + auto span = duration_cast(logger::now() - start); + logger(format("header" BN_READ_ROW) % (to_double(filled) / bucket) % + span.count()); + + // ------------------------------------------------------------------------ + + filled = zero; + bucket = max_size_t; + start = logger::now(); + while (!cancel_ && (++bucket < query_.txs_buckets())) + { + const auto top = query_.top_txs(bucket); + if (!top.is_terminal()) + ++filled; + + if (is_zero(bucket % block_frequency)) + logger(format("txs" BN_READ_ROW) % bucket % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + span = duration_cast(logger::now() - start); + logger(format("txs" BN_READ_ROW) % (to_double(filled) / bucket) % + span.count()); + + // ------------------------------------------------------------------------ + + filled = zero; + bucket = max_size_t; + start = logger::now(); + while (!cancel_ && (++bucket < query_.tx_buckets())) + { + const auto top = query_.top_tx(bucket); + if (!top.is_terminal()) + ++filled; + + if (is_zero(bucket % tx_frequency)) + logger(format("tx" BN_READ_ROW) % bucket % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + span = duration_cast(logger::now() - start); + logger(format("tx" BN_READ_ROW) % (to_double(filled) / bucket) % + span.count()); + + // ------------------------------------------------------------------------ + + filled = zero; + bucket = max_size_t; + start = logger::now(); + while (!cancel_ && (++bucket < query_.point_buckets())) + { + const auto top = query_.top_point(bucket); + if (!top.is_terminal()) + ++filled; + + if (is_zero(bucket % tx_frequency)) + logger(format("point" BN_READ_ROW) % bucket % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + span = duration_cast(logger::now() - start); + logger(format("point" BN_READ_ROW) % (to_double(filled) / bucket) % + span.count()); + + // ------------------------------------------------------------------------ + + filled = zero; + bucket = max_size_t; + start = logger::now(); + while (!cancel_ && (++bucket < query_.spend_buckets())) + { + const auto top = query_.top_spend(bucket); + if (!top.is_terminal()) + ++filled; + + if (is_zero(bucket % put_frequency)) + logger(format("spend" BN_READ_ROW) % bucket % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + span = duration_cast(logger::now() - start); + logger(format("spend" BN_READ_ROW) % (to_double(filled) / bucket) % + span.count()); +} + +// hashmap collision distributions. +// BUGBUG: the vector allocations are exceessive and can result in sigkill. +// BUGBUG: must process each header independently as buckets may not coincide. +void executor::scan_collisions() const +{ + using namespace database; + using hint = header_link::integer; + constexpr auto empty = 0u; + constexpr auto block_frequency = 10'000u; + constexpr auto tx_frequency = 1'000'000u; + constexpr auto put_frequency = 10'000'000u; + constexpr auto count = [](const auto& list) + { + return std::accumulate(list.begin(), list.end(), zero, + [](size_t total, const auto& value) + { + return total + to_int(to_bool(value)); + }); + }; + constexpr auto dump = [&](const auto& list) + { + // map frequency to length. + std::map map{}; + for (const auto value: list) + ++map[value]; + + return map; + }; + + constexpr auto hash = [](const auto& key) + { + constexpr auto length = array_count; + constexpr auto size = std::min(length, sizeof(size_t)); + size_t value{}; + std::copy_n(key.begin(), size, system::byte_cast(value).begin()); + return value; + }; + + logger(BN_OPERATION_INTERRUPT); + + // header & txs (txs is a proxy for validated_bk) + // ------------------------------------------------------------------------ + + auto index = max_size_t; + auto start = logger::now(); + const auto header_buckets = query_.header_buckets(); + const auto header_records = query_.header_records(); + std_vector header(header_buckets, empty); + std_vector txs(header_buckets, empty); + while (!cancel_ && (++index < header_records)) + { + const header_link link{ possible_narrow_cast(index) }; + ++header.at(hash(query_.get_header_key(link.value)) % header_buckets); + ++txs.at(hash((header_link::bytes)link) % header_buckets); + + if (is_zero(index % block_frequency)) + logger(format("header/txs" BN_READ_ROW) % index % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + // ........................................................................ + + const auto header_count = count(header); + auto span = duration_cast(logger::now() - start); + logger(format("header: %1% in %2%s buckets %3% filled %4% rate %5% ") % + index % span.count() % header_buckets % header_count % + (to_double(header_count) / header_buckets)); + + for (const auto& entry: dump(header)) + logger(format("header: %1% frequency: %2%") % + entry.first % entry.second); + + header.clear(); + header.shrink_to_fit(); + + // ........................................................................ + + const auto txs_count = count(txs); + span = duration_cast(logger::now() - start); + logger(format("txs: %1% in %2%s buckets %3% filled %4% rate %5%") % + index % span.count() % header_buckets % txs_count % + (to_double(txs_count) / header_buckets)); + + for (const auto& entry: dump(txs)) + logger(format("txs: %1% frequency: %2%") % + entry.first % entry.second); + + txs.clear(); + txs.shrink_to_fit(); + + // tx & strong_tx (strong_tx is a proxy for validated_tx) + // ------------------------------------------------------------------------ + + index = max_size_t; + start = logger::now(); + const auto tx_buckets = query_.tx_buckets(); + const auto tx_records = query_.tx_records(); + std_vector tx(tx_buckets, empty); + std_vector strong_tx(tx_buckets, empty); + while (!cancel_ && (++index < tx_records)) + { + const tx_link link{ possible_narrow_cast(index) }; + ++tx.at(hash(query_.get_tx_key(link.value)) % tx_buckets); + ++strong_tx.at(hash((tx_link::bytes)link) % tx_buckets); + + if (is_zero(index % tx_frequency)) + logger(format("tx & strong_tx" BN_READ_ROW) % index % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + // ........................................................................ + + const auto tx_count = count(tx); + span = duration_cast(logger::now() - start); + logger(format("tx: %1% in %2%s buckets %3% filled %4% rate %5%") % + index % span.count() % tx_buckets % tx_count % + (to_double(tx_count) / tx_buckets)); + + for (const auto& entry: dump(tx)) + logger(format("tx: %1% frequency: %2%") % + entry.first % entry.second); + + tx.clear(); + tx.shrink_to_fit(); + + // ........................................................................ + + const auto strong_tx_count = count(strong_tx); + span = duration_cast(logger::now() - start); + logger(format("strong_tx: %1% in %2%s buckets %3% filled %4% rate %5%") % + index % span.count() % tx_buckets % strong_tx_count % + (to_double(strong_tx_count) / tx_buckets)); + + for (const auto& entry: dump(strong_tx)) + logger(format("strong_tx: %1% frequency: %2%") % + entry.first % entry.second); + + strong_tx.clear(); + strong_tx.shrink_to_fit(); + + // point + // ------------------------------------------------------------------------ + + index = max_size_t; + start = logger::now(); + const auto point_buckets = query_.point_buckets(); + const auto point_records = query_.point_records(); + std_vector point(point_buckets, empty); + while (!cancel_ && (++index < point_records)) + { + const tx_link link{ possible_narrow_cast(index) }; + ++point.at(hash(query_.get_point_key(link.value)) % point_buckets); + + if (is_zero(index % tx_frequency)) + logger(format("point" BN_READ_ROW) % index % + duration_cast(logger::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + // ........................................................................ + + const auto point_count = count(point); + span = duration_cast(logger::now() - start); + logger(format("point: %1% in %2%s buckets %3% filled %4% rate %5%") % + index % span.count() % point_buckets % point_count % + (to_double(point_count) / point_buckets)); + + for (const auto& entry: dump(point)) + logger(format("point: %1% frequency: %2%") % + entry.first % entry.second); + + point.clear(); + point.shrink_to_fit(); + + // spend + // ------------------------------------------------------------------------ + + auto total = zero; + index = max_size_t; + start = logger::now(); + const auto spend_buckets = query_.spend_buckets(); + std_vector spend(spend_buckets, empty); + while (!cancel_ && (++index < query_.header_records())) + { + const header_link link{ possible_narrow_cast(index) }; + const auto transactions = query_.to_transactions(link); + for (const auto& transaction: transactions) + { + const auto inputs = query_.to_tx_spends(transaction); + for (const auto& in: inputs) + { + ++total; + ++spend.at(hash(query_.to_spend_key(in)) % spend_buckets); + + if (is_zero(index % put_frequency)) + logger(format("spend" BN_READ_ROW) % total % + duration_cast(logger::now() - start).count()); + } + } + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + // ........................................................................ + + const auto spend_count = count(spend); + span = duration_cast(logger::now() - start); + logger(format("spend: %1% in %2%s buckets %3% filled %4% rate %5%") % + total % span.count() % spend_buckets % spend_count % + (to_double(spend_count) / spend_buckets)); + + for (const auto& entry: dump(spend)) + logger(format("spend: %1% frequency: %2%") % + entry.first % entry.second); + + spend.clear(); + spend.shrink_to_fit(); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_store.cpp b/console/executor_store.cpp new file mode 100644 index 000000000..066ad5c84 --- /dev/null +++ b/console/executor_store.cpp @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +// Store functions. +// ---------------------------------------------------------------------------- + +bool executor::check_store_path(bool create) const +{ + const auto& configuration = metadata_.configured.file; + if (configuration.empty()) + { + logger(BN_USING_DEFAULT_CONFIG); + } + else + { + logger(format(BN_USING_CONFIG_FILE) % configuration); + } + + const auto& store = metadata_.configured.database.path; + if (create) + { + logger(format(BN_INITIALIZING_CHAIN) % store); + if (!database::file::create_directory(store)) + { + logger(format(BN_INITCHAIN_EXISTS) % store); + return false; + } + } + else + { + if (!database::file::is_directory(store)) + { + logger(format(BN_UNINITIALIZED_DATABASE) % store); + return false; + } + } + + return true; +} + +bool executor::create_store(bool details) +{ + logger(BN_INITCHAIN_CREATING); + const auto start = logger::now(); + if (const auto ec = store_.create([&](auto event_, auto table) + { + if (details) + logger(format(BN_CREATE) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_INITCHAIN_DATABASE_CREATE_FAILURE) % ec.message()); + return false; + } + + // Create and confirm genesis block (store invalid without it). + logger(BN_INITCHAIN_DATABASE_INITIALIZE); + if (!query_.initialize(metadata_.configured.bitcoin.genesis_block)) + { + logger(BN_INITCHAIN_DATABASE_INITIALIZE_FAILURE); + close_store(details); + return false; + } + + const auto span = duration_cast(logger::now() - start); + logger(format(BN_INITCHAIN_CREATED) % span.count()); + return true; +} + +bool executor::open_store(bool details) +{ + return !open_store_coded(details); +} + +// not timed or announced (generally fast) +code executor::open_store_coded(bool details) +{ + ////logger(BN_DATABASE_STARTING); + if (const auto ec = store_.open([&](auto event_, auto table) + { + if (details) + logger(format(BN_OPEN) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_DATABASE_START_FAIL) % ec.message()); + return ec; + } + + logger(BN_DATABASE_STARTED); + return error::success; +} + +bool executor::close_store(bool details) +{ + logger(BN_DATABASE_STOPPING); + const auto start = logger::now(); + if (const auto ec = store_.close([&](auto event_, auto table) + { + if (details) + logger(format(BN_CLOSE) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_DATABASE_STOP_FAIL) % ec.message()); + return false; + } + + const auto span = duration_cast(logger::now() - start); + logger(format(BN_DATABASE_TIMED_STOP) % span.count()); + return true; +} + +bool executor::reload_store(bool details) +{ + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return false; + } + + if (const auto ec = store_.get_fault()) + { + logger(format(BN_RELOAD_INVALID) % ec.message()); + return false; + } + + logger(BN_NODE_RELOAD_STARTED); + const auto start = logger::now(); + if (const auto ec = node_->reload([&](auto event_, auto table) + { + if (details) + logger(format(BN_RELOAD) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_NODE_RELOAD_FAIL) % ec.message()); + return false; + }; + + node_->resume(); + const auto span = duration_cast(logger::now() - start); + logger(format(BN_NODE_RELOAD_COMPLETE) % span.count()); + return true; +} + +bool executor::restore_store(bool details) +{ + logger(BN_RESTORING_CHAIN); + const auto start = logger::now(); + if (const auto ec = store_.restore([&](auto event_, auto table) + { + if (details) + logger(format(BN_RESTORE) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + if (ec == database::error::flush_lock) + logger(BN_RESTORE_MISSING_FLUSH_LOCK); + else + logger(format(BN_RESTORE_FAILURE) % ec.message()); + + return false; + } + + const auto span = duration_cast(logger::now() - start); + logger(format(BN_RESTORE_COMPLETE) % span.count()); + return true; +} + +bool executor::hot_backup_store(bool details) +{ + if (!node_) + { + logger(BN_NODE_UNAVAILABLE); + return false; + } + + if (const auto ec = store_.get_fault()) + { + logger(format(BN_SNAPSHOT_INVALID) % ec.message()); + return false; + } + + logger(BN_NODE_BACKUP_STARTED); + const auto start = logger::now(); + if (const auto ec = node_->snapshot([&](auto event_, auto table) + { + if (details) + logger(format(BN_BACKUP) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_NODE_BACKUP_FAIL) % ec.message()); + return false; + } + + node_->resume(); + const auto span = duration_cast(logger::now() - start); + logger(format(BN_NODE_BACKUP_COMPLETE) % span.count()); + return true; +} + +bool executor::cold_backup_store(bool details) +{ + logger(BN_NODE_BACKUP_STARTED); + const auto start = logger::now(); + if (const auto ec = store_.snapshot([&](auto event_, auto table) + { + if (details) + logger(format(BN_BACKUP) % + full_node::store::events.at(event_) % + full_node::store::tables.at(table)); + })) + { + logger(format(BN_NODE_BACKUP_FAIL) % ec.message()); + return false; + } + + const auto span = duration_cast(logger::now() - start); + logger(format(BN_NODE_BACKUP_COMPLETE) % span.count()); + return true; +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_test_reader.cpp b/console/executor_test_reader.cpp new file mode 100644 index 000000000..40e910807 --- /dev/null +++ b/console/executor_test_reader.cpp @@ -0,0 +1,845 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +#if defined(UNDEFINED) + +void executor::read_test(bool dump) const +{ + constexpr auto start_tx = 1'000'000'000_u32; + constexpr auto target_count = 100_size; + + // Set ensures unique addresses. + std::set keys{}; + auto tx = start_tx; + + logger(format("Getting first [%1%] output address hashes.") % target_count); + + auto start = fine_clock::now(); + while (!cancel_ && keys.size() < target_count) + { + const auto outputs = query_.get_outputs(tx++); + if (outputs->empty()) + return; + + for (const auto& put: *outputs) + { + keys.emplace(put->script().hash()); + if (cancel_ || keys.size() == target_count) + break; + } + } + + auto span = duration_cast(fine_clock::now() - start); + logger(format("Got first [%1%] unique addresses above tx [%2%] in [%3%] ms.") % + keys.size() % start_tx % span.count()); + + struct out + { + hash_digest address; + + uint32_t bk_fk; + uint32_t bk_height; + hash_digest bk_hash; + + uint32_t tx_fk; + size_t tx_position; + hash_digest tx_hash; + + uint32_t sp_tx_fk; + hash_digest sp_tx_hash; + + uint64_t input_fk; + chain::input::cptr input{}; + + uint64_t output_fk; + chain::output::cptr output{}; + }; + + std_vector outs{}; + outs.reserve(target_count); + using namespace database; + + start = fine_clock::now(); + for (auto& key: keys) + { + size_t found{}; + auto address_it = store_.address.it(key); + if (cancel_ || address_it.self().is_terminal()) + return; + + do + { + table::address::record address{}; + if (cancel_ || !store_.address.get(address_it.self(), address)) + return; + + const auto out_fk = address.output_fk; + table::output::get_parent output{}; + if (!store_.output.get(out_fk, output)) + return; + + const auto tx_fk = output.parent_fk; + const auto block_fk = query_.to_block(tx_fk); + + table::header::get_height header{}; + if (!store_.header.get(block_fk, header)) + return; + + table::txs::get_position txs{ {}, tx_fk }; + if (!store_.txs.get(query_.to_txs(block_fk), txs)) + return; + + spend_link sp_fk{}; + input_link in_fk{}; + tx_link sp_tx_fk{}; + + // Get first spender only (may or may not be confirmed). + const auto spenders = query_.to_spenders(out_fk); + if (!spenders.empty()) + { + sp_fk = spenders.front(); + table::spend::record spend{}; + if (!store_.spend.get(sp_fk, spend)) + return; + + in_fk = spend.input_fk; + sp_tx_fk = spend.parent_fk; + } + + ++found; + outs.push_back(out + { + key, + + block_fk, + header.height, + query_.get_header_key(block_fk), + + tx_fk, + txs.position, + query_.get_tx_key(tx_fk), + + sp_tx_fk, + query_.get_tx_key(sp_tx_fk), + + in_fk, + query_.get_input(sp_fk), + + out_fk, + query_.get_output(out_fk) + }); + } + while (address_it.advance()); + + logger(format("Fetched [%1%] unique payments to address [%2%].") % + found% encode_hash(key)); + } + + span = duration_cast(fine_clock::now() - start); + logger(format("Got all [%1%] payments to [%2%] addresses in [%3%] ms.") % + outs.size() % keys.size() % span.count()); + + if (!dump) + return; + + // Write it all... + logger( + "output_script_hash, " + + "ouput_bk_fk, " + "ouput_bk_height, " + "ouput_bk_hash, " + + "ouput_tx_fk, " + "ouput_tx_position, " + "ouput_tx_hash, " + + "input_tx_fk, " + "input_tx_hash, " + + "output_fk, " + "output_script, " + + "input_fk, " + "input_script" + ); + + for (const auto& row: outs) + { + if (cancel_) break; + + const auto output = !row.output ? "{error}" : + row.output->script().to_string(chain::flags::all_rules); + + const auto input = !row.input ? "{unspent}" : + row.input->script().to_string(chain::flags::all_rules); + + logger(format("%1%, %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%, %11%, %12%, %13%") % + encode_hash(row.address) % + + row.bk_fk % + row.bk_height % + encode_hash(row.bk_hash) % + + row.tx_fk % + row.tx_position % + encode_hash(row.tx_hash) % + + row.sp_tx_fk % + encode_hash(row.sp_tx_hash) % + + row.output_fk % + output % + + row.input_fk % + input); + } +} + +// arbitrary testing (const). +void executor::read_test(bool dump) const +{ + logger("Wire size computation."); + const auto start = fine_clock::now(); + const auto last = metadata_.configured.node.maximum_height; + + size_t size{}; + for (auto height = zero; !cancel_ && height <= last; ++height) + { + const auto link = query_.to_candidate(height); + if (link.is_terminal()) + { + logger(format("Max candidate height is (%1%).") % sub1(height)); + return; + } + + const auto bytes = query_.get_block_size(link); + if (is_zero(bytes)) + { + logger(format("Block (%1%) is not associated.") % height); + return; + } + + size += bytes; + } + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("Wire size (%1%) at (%2%) in (%3%) ms.") % + size % last % span.count()); +} + +void executor::read_test(bool dump) const +{ + auto start = fine_clock::now(); + auto count = query_.header_records(); + uint32_t block{ one }; + + logger("Find strong blocks."); + while (!cancel_ && (block < count) && query_.is_strong_block(block)) + { + ++block; + } + + auto span = duration_cast(fine_clock::now() - start); + logger(format("Top strong block is [%1%] in [%2%] ms.") % sub1(block) % span.count()); + start = fine_clock::now(); + count = query_.header_records(); + uint32_t milestone{ 295'001 }; + + logger("Find milestone blocks."); + while (!cancel_ && (milestone < count) && query_.is_milestone(milestone)) + { + ++milestone; + } + + span = duration_cast(fine_clock::now() - start); + logger(format("Top milestone block is [%1%] in [%2%] ms.") % sub1(milestone) % span.count()); + start = fine_clock::now(); + uint32_t tx{ one }; + + logger("Find strong txs."); + count = query_.tx_records(); + while (!cancel_ && (tx < count) && query_.is_strong_tx(tx)) + { + ++tx; + } + + span = duration_cast(fine_clock::now() - start); + logger(format("Top strong tx is [%1%] in [%2%] ms.") % sub1(tx) % span.count()); +} + +void executor::read_test(bool dump) const +{ + const auto from = 481'824_u32; + const auto top = 840'000_u32; ////query_.get_top_associated(); + const auto start = fine_clock::now(); + + // segwit activation + uint32_t block{ from }; + size_t total{}; + + logger("Get all coinbases."); + while (!cancel_ && (block <= top)) + { + const auto count = query_.get_tx_count(query_.to_candidate(block++)); + if (is_zero(count)) + return; + + total += system::ceilinged_log2(count); + } + + const auto average = total / (top - from); + const auto span = duration_cast(fine_clock::now() - start); + logger(format("Total block depths [%1%] to [%2%] avg [%3%] in [%4%] ms.") + % total % top % average % span.count()); +} + +void executor::read_test(bool dump) const +{ + // Binance wallet address with 1,380,169 transaction count. + // blockstream.info/address/bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h + const auto data = base16_array("0014dc6bf86354105de2fcd9868a2b0376d6731cb92f"); + const chain::script output_script{ data, false }; + const auto mnemonic = output_script.to_string(chain::flags::all_rules); + logger(format("Getting payments to {%1%}.") % mnemonic); + + const auto start = fine_clock::now(); + database::output_links outputs{}; + if (!query_.to_address_outputs(outputs, output_script.hash())) + return; + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("Found [%1%] outputs of {%2%} in [%3%] ms.") % + outputs.size() % mnemonic % span.count()); +} + +// This was caused by concurrent redundant downloads at tail following restart. +// The earlier transactions were marked as confirmed and during validation the +// most recent are found via point.hash association priot to to_block() test. +void executor::read_test(bool dump) const +{ + const auto height = 839'287_size; + const auto block = query_.to_confirmed(height); + if (block.is_terminal()) + { + logger("!block"); + return; + } + + const auto txs = query_.to_transactions(block); + if (txs.empty()) + { + logger("!txs"); + return; + } + + database::tx_link spender_link{}; + const auto hash_spender = system::base16_hash("1ff970ec310c000595929bd290bbc8f4603ee18b2b4e3239dfb072aaca012b28"); + for (auto position = zero; !cancel_ && position < txs.size(); ++position) + { + const auto temp = txs.at(position); + if (query_.get_tx_key(temp) == hash_spender) + { + spender_link = temp; + break; + } + } + + auto spenders = store_.tx.it(hash_spender); + if (spenders.self().is_terminal()) + return; + + // ...260, 261 + size_t spender_count{}; + do + { + const auto foo = spenders.self(); + ++spender_count; + } while(spenders.advance()); + + if (is_zero(spender_count)) + { + logger("is_zero(spender_count)"); + return; + } + + // ...260 + if (spender_link.is_terminal()) + { + logger("spender_link.is_terminal()"); + return; + } + + const auto spender_link1 = query_.to_tx(hash_spender); + if (spender_link != spender_link1) + { + logger("spender_link != spender_link1"); + ////return; + } + + database::tx_link spent_link{}; + const auto hash_spent = system::base16_hash("85f65b57b88b74fd945a66a6ba392a5f3c8a7c0f78c8397228dece885d788841"); + for (auto position = zero; !cancel_ && position < txs.size(); ++position) + { + const auto temp = txs.at(position); + if (query_.get_tx_key(temp) == hash_spent) + { + spent_link = temp; + break; + } + } + + auto spent = store_.tx.it(hash_spent); + if (spent.self().is_terminal()) + return; + + // ...255, 254 + size_t spent_count{}; + do + { + const auto bar = spent.self(); + ++spent_count; + } while (spent.advance()); + + if (is_zero(spent_count)) + { + logger("is_zero(spent_count)"); + return; + } + + // ...254 (not ...255) + if (spent_link.is_terminal()) + { + logger("spent_link.is_terminal()"); + return; + } + + const auto spent_link1 = query_.to_tx(hash_spent); + if (spent_link != spent_link1) + { + logger("spent_link != spent_link1"); + ////return; + } + + const auto tx = query_.to_tx(hash_spender); + if (tx.is_terminal()) + { + logger("!tx"); + return; + } + + if (tx != spender_link) + { + logger("tx != spender_link"); + return; + } + + if (spender_link <= spent_link) + { + logger("spender_link <= spent_link"); + return; + } + + // ...254 + const auto header1 = query_.to_block(spender_link); + if (header1.is_terminal()) + { + logger("header1.is_terminal()"); + return; + } + + // ...255 (the latter instance is not confirmed) + const auto header11 = query_.to_block(add1(spender_link)); + if (!header11.is_terminal()) + { + logger("!header11.is_terminal()"); + return; + } + + // ...260 + const auto header2 = query_.to_block(spent_link); + if (header2.is_terminal()) + { + logger("auto.is_terminal()"); + return; + } + + // ...261 (the latter instance is not confirmed) + const auto header22 = query_.to_block(add1(spent_link)); + if (!header22.is_terminal()) + { + logger("!header22.is_terminal()"); + return; + } + + if (header1 != header2) + { + logger("header1 != header2"); + return; + } + + if (header1 != block) + { + logger("header1 != block"); + return; + } + + const auto ec = query_.block_confirmable(query_.to_confirmed(height)); + logger(format("Confirm [%1%] test (%2%).") % height % ec.message()); +} + +void executor::read_test(bool dump) const +{ + const auto bk_link = query_.to_candidate(804'001_size); + const auto block = query_.get_block(bk_link); + if (!block) + { + logger("!query_.get_block(link)"); + return; + } + + ////const auto tx = query_.get_transaction({ 980'984'671_u32 }); + ////if (!tx) + ////{ + //// logger("!query_.get_transaction(tx_link)"); + //// return; + ////} + //// + ////chain::context ctx{}; + ////if (!query_.get_context(ctx, bk_link)) + ////{ + //// logger("!query_.get_context(ctx, bk_link)"); + //// return; + ////} + //// + ////if (!query_.populate_with_metadata(*tx)) + ////{ + //// logger("!query_.populate_with_metadata(*tx)"); + //// return; + ////} + //// + ////if (const auto ec = tx->confirm(ctx)) + //// logger(format("Error confirming tx [980'984'671] %1%") % ec.message()); + //// + ////// Does not compute spent metadata, assumes coinbase spent and others not. + ////if (!query_.populate_with_metadata(*block)) + ////{ + //// logger("!query_.populate_with_metadata(*block)"); + //// return; + ////} + //// + ////const auto& txs = *block->transactions_ptr(); + ////if (txs.empty()) + ////{ + //// logger("txs.empty()"); + //// return; + ////} + //// + ////for (auto index = one; index < txs.size(); ++index) + //// if (const auto ec = txs.at(index)->confirm(ctx)) + //// logger(format("Error confirming tx [%1%] %2%") % index % ec.message()); + //// + ////logger("Confirm test 1 complete."); + + const auto ec = query_.block_confirmable(bk_link); + logger(format("Confirm test 2 complete (%1%).") % ec.message()); +} + +void executor::read_test(bool dump) const +{ + using namespace database; + constexpr auto frequency = 100'000u; + const auto start = fine_clock::now(); + auto tx = 664'400'000_size; + + // Read all data except genesis (ie. for validation). + while (!cancel_ && (++tx < query_.tx_records())) + { + const tx_link link{ + system::possible_narrow_cast(tx) }; + + ////const auto ptr = query_.get_header(link); + ////if (!ptr) + ////{ + //// logger("Failure: get_header"); + //// break; + ////} + ////else if (is_zero(ptr->bits())) + ////{ + //// logger("Failure: zero bits"); + //// break; + ////} + + ////const auto txs = query_.to_transactions(link); + ////if (txs.empty()) + ////{ + //// logger("Failure: to_txs"); + //// break; + ////} + + const auto ptr = query_.get_transaction(link); + if (!ptr) + { + logger("Failure: get_transaction"); + break; + } + else if (!ptr->is_valid()) + { + logger("Failure: is_valid"); + break; + } + + if (is_zero(tx % frequency)) + logger(format("get_transaction" BN_READ_ROW) % tx % + duration_cast(fine_clock::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("get_transaction" BN_READ_ROW) % tx % span.count()); +} + +void executor::read_test(bool dump) const +{ + constexpr auto hash492224 = base16_hash( + "0000000000000000003277b639e56dffe2b4e60d18aeedb1fe8b7e4256b2a526"); + + logger("HIT TO START"); + std::string line{}; + std::getline(input_, line); + const auto start = fine_clock::now(); + + for (size_t height = 492'224; (height <= 492'224) && !cancel_; ++height) + { + // 2s 0s + const auto link = query_.to_header(hash492224); + if (link.is_terminal()) + { + logger("to_header"); + return; + } + + ////const auto link = query_.to_confirmed(height); + ////if (link.is_terminal()) + ////{ + //// logger("to_confirmed"); + //// return; + ////} + + // 109s 111s + const auto block = query_.get_block(link); + if (!block || !block->is_valid() || block->hash() != hash492224) + { + logger("get_block"); + return; + } + + // 125s 125s + code ec{}; + if ((ec = block->check())) + { + logger(format("Block [%1%] check1: %2%") % height % ec.message()); + return; + } + + // 117s 122s + if (chain::checkpoint::is_conflict( + metadata_.configured.bitcoin.checkpoints, block->hash(), height)) + { + logger(format("Block [%1%] checkpoint conflict") % height); + return; + } + + ////// ???? 125s/128s + ////block->populate(); + + // 191s 215s/212s/208s [independent] + // ???? 228s/219s/200s [combined] + if (!query_.populate(*block)) + { + logger("populate"); + return; + } + + // 182s + database::context ctx{}; + if (!query_.get_context(ctx, link) || ctx.height != height) + { + logger("get_context"); + return; + } + + // Fabricate chain_state context from store context. + chain::context state{}; + state.flags = ctx.flags; + state.height = ctx.height; + state.median_time_past = ctx.mtp; + state.timestamp = block->header().timestamp(); + + // split from accept. + if ((ec = block->check(state))) + { + logger(format("Block [%1%] check2: %2%") % height % ec.message()); + return; + } + + // 199s + const auto& coin = metadata_.configured.bitcoin; + if ((ec = block->accept(state, coin.subsidy_interval_blocks, + coin.initial_subsidy()))) + { + logger(format("Block [%1%] accept: %2%") % height % ec.message()); + return; + } + + // 1410s + if ((ec = block->connect(state))) + { + logger(format("Block [%1%] connect: %2%") % height % ec.message()); + return; + } + + ////for (size_t index = one; index < block->transactions_ptr()->size(); ++index) + ////{ + //// constexpr size_t index = 1933; + //// const auto& tx = *block->transactions_ptr()->at(index); + //// if ((ec = tx.connect(state))) + //// { + //// logger(format("Tx (%1%) [%2%] %3%") + //// % index + //// % encode_hash(tx.hash(false)) + //// % ec.message()); + //// } + ////} + + // +10s for all. + logger(format("block:%1%") % height); + ////logger(format("block:%1% flags:%2% mtp:%3%") % + //// ctx.height % ctx.flags % ctx.mtp); + } + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("STOP (%1% secs)") % span.count()); +} + +#endif // UNDEFINED + +// TODO: create a block/tx dumper. +void executor::read_test(bool) const +{ + constexpr auto hash511280 = base16_hash( + "00000000000000000030b12ee5a31aaf553f49cdafa52698f70f0f0706f46d3d"); + + const auto start = logger::now(); + const auto link = query_.to_header(hash511280); + if (link.is_terminal()) + { + logger("link.is_terminal()"); + return; + } + + const auto block = query_.get_block(link); + if (!block) + { + logger("!block"); + return; + } + + if (!block->is_valid()) + { + logger("!block->is_valid()"); + return; + } + + database::context ctx{}; + if (!query_.get_context(ctx, link)) + { + logger("!query_.get_context(ctx, link)"); + return; + } + + logger(format("flags:%1% height:%2% mtp:%3%") % + ctx.flags % ctx.height % ctx.mtp); + + // minimum_block_version and work_required are only for header validate. + chain::context state{}; + state.flags = ctx.flags; + state.height = ctx.height; + state.median_time_past = ctx.mtp; + state.timestamp = block->header().timestamp(); + state.minimum_block_version = 0; + state.work_required = 0; + if (!query_.populate(*block)) + { + logger("!query_.populate(*block)"); + return; + } + + code ec{}; + if ((ec = block->check())) + { + logger(format("Block check: %1%") % ec.message()); + return; + } + + const auto& coin = metadata_.configured.bitcoin; + if ((ec = block->accept(state, coin.subsidy_interval_blocks, + coin.initial_subsidy()))) + { + logger(format("Block accept: %1%") % ec.message()); + return; + } + + if ((ec = block->connect(state))) + { + logger(format("Block connect: %1%") % ec.message()); + return; + } + + const auto span = duration_cast(logger::now() - start); + logger(format("Validated block 511280 in %1% msec.") % span.count()); +} + +} // namespace node +} // namespace libbitcoin diff --git a/console/executor_test_writer.cpp b/console/executor_test_writer.cpp new file mode 100644 index 000000000..5ef603d99 --- /dev/null +++ b/console/executor_test_writer.cpp @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "executor.hpp" +#include "localize.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace libbitcoin { +namespace node { + +using boost::format; +using system::config::printer; +using namespace network; +using namespace system; +using namespace std::chrono; +using namespace std::placeholders; + +// arbitrary testing (non-const). +void executor::write_test(bool) +{ + logger("No write test implemented."); +} + +#if defined(UNDEFINED) + +void executor::write_test() +{ + code ec{}; + size_t count{}; + const auto start = fine_clock::now(); + + const auto fork = query_.get_fork(); + const auto top_associated = query_.get_top_associated_from(fork); + + for (auto height = fork; !cancel_ && !ec && height <= top_associated; + ++height, ++count) + { + const auto block = query_.to_candidate(height); + if (!query_.set_strong(block)) + { + logger(format("set_strong [%1%] fault.") % height); + return; + } + + ////if (ec = query_.block_confirmable(block)) + ////{ + //// logger(format("block_confirmable [%1%] fault (%2%).") % height % ec.message()); + //// return; + ////} + //// + ////if (!query_.set_block_confirmable(block, uint64_t{})) + ////{ + //// logger(format("set_block_confirmable [%1%] fault.") % height); + //// return; + ////} + + if (!query_.push_confirmed(block)) + { + logger(format("push_confirmed [%1%] fault.") % height); + return; + } + + if (is_zero(height % 1000_size)) + logger(format("write_test [%1%].") % height); + } + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("Set confirmation of %1% blocks in %2% secs.") % count % + span.count()); +} + +void executor::write_test() +{ + using namespace database; + constexpr auto frequency = 10'000; + const auto start = fine_clock::now(); + logger(BN_OPERATION_INTERRUPT); + + auto height = query_.get_top_candidate(); + while (!cancel_ && (++height < query_.header_records())) + { + // Assumes height is header link. + const header_link link{ possible_narrow_cast(height) }; + + if (!query_.push_confirmed(link)) + { + logger("!query_.push_confirmed(link)"); + return; + } + + if (!query_.push_candidate(link)) + { + logger("!query_.push_candidate(link)"); + return; + } + + if (is_zero(height % frequency)) + logger(format("block" BN_WRITE_ROW) % height % + duration_cast(fine_clock::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("block" BN_WRITE_ROW) % height % span.count()); +} + +void executor::write_test() +{ + using namespace database; + ////constexpr uint64_t fees = 99; + constexpr auto frequency = 10'000; + const auto start = fine_clock::now(); + code ec{}; + + logger(BN_OPERATION_INTERRUPT); + + auto height = zero;//// query_.get_top_confirmed(); + const auto records = query_.header_records(); + while (!cancel_ && (++height < records)) + { + // Assumes height is header link. + const header_link link{ possible_narrow_cast(height) }; + + if (!query_.set_strong(link)) + { + // total sequential chain cost: 18.7 min (now 6.6). + logger("Failure: set_strong"); + break; + } + else if ((ec = query_.block_confirmable(link))) + { + // must set_strong before each (no push, verifies non-use). + logger(format("Failure: block_confirmed, %1%") % ec.message()); + break; + } + ////if (!query_.set_txs_connected(link)) + ////{ + //// // total sequential chain cost: 21 min. + //// logger("Failure: set_txs_connected"); + //// break; + ////} + ////if (!query_.set_block_confirmable(link, fees)) + ////{ + //// // total chain cost: 1 sec. + //// logger("Failure: set_block_confirmable"); + //// break; + //// break; + ////} + ////else if (!query_.push_candidate(link)) + ////{ + //// // total chain cost: 1 sec. + //// logger("Failure: push_candidate"); + //// break; + ////} + ////else if (!query_.push_confirmed(link)) + ////{ + //// // total chain cost: 1 sec. + //// logger("Failure: push_confirmed"); + //// break; + ////} + + if (is_zero(height % frequency)) + logger(format("block" BN_WRITE_ROW) % height % + duration_cast(fine_clock::now() - start).count()); + } + + if (cancel_) + logger(BN_OPERATION_CANCELED); + + const auto span = duration_cast(fine_clock::now() - start); + logger(format("block" BN_WRITE_ROW) % height % span.count()); +} + +void executor::write_test() +{ + constexpr auto hash251684 = base16_hash( + "00000000000000720e4c59ad28a8b61f38015808e92465e53111e3463aed80de"); + const auto link = query_.to_header(hash251684); + if (link.is_terminal()) + { + logger("link.is_terminal()"); + return; + } + + if (query_.confirmed_records() != 251684u) + { + logger("!query_.confirmed_records() != 251684u"); + return; + } + + if (!query_.push_confirmed(link)) + { + logger("!query_.push_confirmed(link)"); + return; + } + + if (query_.confirmed_records() != 251685u) + { + logger("!query_.confirmed_records() != 251685u"); + return; + } + + logger("Successfully confirmed block 251684."); +} + +#endif // UNDEFINED + +} // namespace node +} // namespace libbitcoin diff --git a/console/main.cpp b/console/main.cpp index 66e465422..99f5ee3ed 100644 --- a/console/main.cpp +++ b/console/main.cpp @@ -50,7 +50,7 @@ int wmain(int argc, wchar_t* argv[]) } // This is invoked by dump_stack_trace. -void handle_stack_trace(const std::string& trace) NOEXCEPT +void handle_stack_trace(const std::string& trace) { if (trace.empty()) { @@ -64,7 +64,7 @@ void handle_stack_trace(const std::string& trace) NOEXCEPT } // This is invoked by dump_stack_trace. -std::wstring pdb_path() NOEXCEPT +std::wstring pdb_path() { return bc::system::to_extended_path(symbols_path); } diff --git a/console/stack_trace.cpp b/console/stack_trace.cpp index 1eec49e43..a0cd06dd7 100644 --- a/console/stack_trace.cpp +++ b/console/stack_trace.cpp @@ -39,8 +39,8 @@ #pragma comment(lib, "dbghelp.lib") // Must define pdb_path() and handle_stack_trace when using dump_stack_trace. -extern std::wstring pdb_path() NOEXCEPT; -extern void handle_stack_trace(const std::string& trace) NOEXCEPT; +extern std::wstring pdb_path(); +extern void handle_stack_trace(const std::string& trace); constexpr size_t depth_limit{ 10 }; @@ -55,7 +55,7 @@ struct module_data void* dll_base; }; -inline STACKFRAME64 get_stack_frame(const CONTEXT& context) NOEXCEPT +inline STACKFRAME64 get_stack_frame(const CONTEXT& context) { STACKFRAME64 frame{}; @@ -78,7 +78,7 @@ inline STACKFRAME64 get_stack_frame(const CONTEXT& context) NOEXCEPT return frame; } -static std::string get_undecorated(HANDLE process, DWORD64 address) NOEXCEPT +static std::string get_undecorated(HANDLE process, DWORD64 address) { // Including null terminator. constexpr DWORD maximum_characters{ 1024 }; From a818a3cacf0b60b1b01e092f511cf02215c18ab6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 10 Dec 2024 20:19:31 -0500 Subject: [PATCH 2/2] const auto txs_ptr = block->transactions_ptr(); --- include/bitcoin/node/impl/chasers/chaser_organize.ipp | 2 ++ src/protocols/protocol_block_in_31800.cpp | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index a85cf4e34..fa540fb40 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -125,6 +125,7 @@ void CLASS::do_organize(typename Block::cptr block, using namespace system; BC_ASSERT(stranded()); + // shared_ptr copy keeps block ref in scope until completion of set_code. const auto& hash = block->get_hash(); const auto& header = get_header(*block); auto& query = archive(); @@ -564,6 +565,7 @@ code CLASS::push_block(const system::hash_digest& key) NOEXCEPT if (!handle) return error::organize15; + // handle keeps the block reference in scope until completion of set_code. const auto& value = handle.mapped(); return push_block(*value.block, value.state->context()); } diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index e48b7cc57..ef9fd9d14 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -351,8 +351,11 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, // Commit block.txs. // ........................................................................ + // This must not be a reference to the shared pointer, as otherwise the + // shared_ptr may be taken out of scope before the tx write completes. + // set_code() uses weak references to many elements of the transaction ref. + const auto txs_ptr = block->transactions_ptr(); const auto size = block->serialized_size(true); - const auto& txs_ptr = block->transactions_ptr(); // This invokes set_strong when checked. if (const auto code = query.set_code(*txs_ptr, link, size, checked))