diff --git a/console/executor.hpp b/console/executor.hpp index 64e2863b..76933994 100644 --- a/console/executor.hpp +++ b/console/executor.hpp @@ -109,8 +109,8 @@ class executor bool do_slabs(); bool do_buckets(); bool do_collisions(); - bool do_read(); - bool do_write(); + bool do_read(const system::hash_digest& hash); + bool do_write(const system::hash_digest& hash); // Runtime options. void do_hot_backup(); @@ -126,8 +126,8 @@ class executor void subscribe_capture(); // Built in tests. - void read_test(bool dump) const; - void write_test(bool dump); + void read_test(const system::hash_digest& hash) const; + void write_test(const system::hash_digest& hash); // Logging. database::file::stream::out::rotator create_log_sink() const; diff --git a/console/executor_commands.cpp b/console/executor_commands.cpp index ac1f862b..e2721c3a 100644 --- a/console/executor_commands.cpp +++ b/console/executor_commands.cpp @@ -168,26 +168,26 @@ bool executor::do_collisions() } // --[t]read -bool executor::do_read() +bool executor::do_read(const system::hash_digest& hash) { log_.stop(); if (!check_store_path() || !open_store()) return false; - read_test(true); + read_test(hash); return close_store(); } // --[w]rite -bool executor::do_write() +bool executor::do_write(const system::hash_digest& hash) { log_.stop(); if (!check_store_path() || !open_store()) return false; - write_test(true); + write_test(hash); return close_store(); } @@ -210,6 +210,9 @@ bool executor::dispatch() if (config.backup) return do_backup(); + if (config.restore) + return do_restore(); + if (config.hardware) return do_hardware(); @@ -228,20 +231,17 @@ bool executor::dispatch() 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.test != system::null_hash) + return do_read(config.test); - if (config.restore) - return do_restore(); + if (config.write != system::null_hash) + return do_write(config.write); return do_run(); } diff --git a/console/executor_options.cpp b/console/executor_options.cpp index 0947c9fa..167e7b3d 100644 --- a/console/executor_options.cpp +++ b/console/executor_options.cpp @@ -197,7 +197,7 @@ void executor::do_menu() const // [t]est void executor::do_test() const { - read_test(false); + read_test(system::null_hash); } // [w]ork diff --git a/console/executor_test_reader.cpp b/console/executor_test_reader.cpp index 9bf7c9ed..8ca9aff2 100644 --- a/console/executor_test_reader.cpp +++ b/console/executor_test_reader.cpp @@ -31,7 +31,7 @@ using namespace network; using namespace system; // arbitrary testing (const). -void executor::read_test(bool) const +void executor::read_test(const hash_digest&) const { logger(format("Point table body searches: %1% / (%2% + %1%)") % store_.point.positive_search_count() % @@ -40,11 +40,12 @@ void executor::read_test(bool) const #if defined(UNDEFINED) -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { using namespace database; constexpr auto start_tx = 1'000'000_u32; constexpr auto target_count = 100_size; + constexpr auto dump{ false }; // Set ensures unique addresses. std::set keys{}; @@ -271,7 +272,7 @@ void executor::read_test(bool dump) const } } -void executor::read_test(bool) const +void executor::read_test(const hash_digest&) const { database::header_link link{ 350'017_u32 }; const auto ec = query_.block_confirmable(link); @@ -279,7 +280,7 @@ void executor::read_test(bool) const query_.get_height(link)); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { logger("Wire size computation."); const auto start = fine_clock::now(); @@ -310,7 +311,7 @@ void executor::read_test(bool dump) const size % last % span.count()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { auto start = fine_clock::now(); auto count = query_.header_records(); @@ -350,7 +351,7 @@ void executor::read_test(bool dump) const logger(format("Top strong tx is [%1%] in [%2%] ms.") % sub1(tx) % span.count()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { const auto from = 481'824_u32; const auto top = 840'000_u32; ////query_.get_top_associated(); @@ -376,7 +377,7 @@ void executor::read_test(bool dump) const % total % top % average % span.count()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { // Binance wallet address with 1,380,169 transaction count. // blockstream.info/address/bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h @@ -398,7 +399,7 @@ void executor::read_test(bool dump) const // 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 +void executor::read_test(const hash_digest&) const { const auto height = 839'287_size; const auto block = query_.to_confirmed(height); @@ -570,7 +571,7 @@ void executor::read_test(bool dump) const logger(format("Confirm [%1%] test (%2%).") % height % ec.message()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { const auto bk_link = query_.to_candidate(804'001_size); const auto block = query_.get_block(bk_link); @@ -627,7 +628,7 @@ void executor::read_test(bool dump) const logger(format("Confirm test 2 complete (%1%).") % ec.message()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { using namespace database; constexpr auto frequency = 100'000u; @@ -683,7 +684,7 @@ void executor::read_test(bool dump) const logger(format("get_transaction" BN_READ_ROW) % tx % span.count()); } -void executor::read_test(bool dump) const +void executor::read_test(const hash_digest&) const { constexpr auto hash492224 = base16_hash( "0000000000000000003277b639e56dffe2b4e60d18aeedb1fe8b7e4256b2a526"); @@ -806,7 +807,7 @@ void executor::read_test(bool dump) const } // TODO: create a block/tx dumper. -void executor::read_test(bool) const +void executor::read_test(const hash_digest&) const { constexpr auto link = 600'000_size; const auto start = logger::now(); diff --git a/console/executor_test_writer.cpp b/console/executor_test_writer.cpp index 119e14e5..2bad8bc9 100644 --- a/console/executor_test_writer.cpp +++ b/console/executor_test_writer.cpp @@ -30,14 +30,14 @@ using namespace network; using namespace system; // arbitrary testing (non-const). -void executor::write_test(bool) +void executor::write_test(const hash_digest&) { logger("No write test implemented."); } #if defined(UNDEFINED) -void executor::write_test(bool) +void executor::write_test(const system::hash_digest&) { for (database::header_link link{ 793'008_u32 }; link < 885'000_u32; ++link) { @@ -51,7 +51,7 @@ void executor::write_test(bool) logger(format("set_block_unknown complete.")); } -void executor::write_test(bool) +void executor::write_test(const system::hash_digest&) { code ec{}; size_t count{}; @@ -97,7 +97,7 @@ void executor::write_test(bool) span.count()); } -void executor::write_test(bool) +void executor::write_test(const system::hash_digest&) { using namespace database; constexpr auto frequency = 10'000; @@ -134,7 +134,7 @@ void executor::write_test(bool) logger(format("block" BN_WRITE_ROW) % height % span.count()); } -void executor::write_test(bool) +void executor::write_test(const system::hash_digest&) { using namespace database; ////constexpr uint64_t fees = 99; @@ -201,7 +201,7 @@ void executor::write_test(bool) logger(format("block" BN_WRITE_ROW) % height % span.count()); } -void executor::write_test(bool) +void executor::write_test(const system::hash_digest&) { constexpr auto hash251684 = base16_hash( "00000000000000720e4c59ad28a8b61f38015808e92465e53111e3463aed80de"); diff --git a/include/bitcoin/node/configuration.hpp b/include/bitcoin/node/configuration.hpp index 62f98917..bb0377fa 100644 --- a/include/bitcoin/node/configuration.hpp +++ b/include/bitcoin/node/configuration.hpp @@ -55,8 +55,8 @@ class BCN_API configuration bool collisions{}; /// Ad-hoc Testing. - bool test{}; - bool write{}; + system::config::hash256 test{}; + system::config::hash256 write{}; /// Settings. log::settings log; diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp index 55f557b2..35eaefa7 100644 --- a/include/bitcoin/node/protocols/protocol_explore.hpp +++ b/include/bitcoin/node/protocols/protocol_explore.hpp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_EXPLORE_HPP #define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_EXPLORE_HPP +#include #include #include #include @@ -95,7 +96,7 @@ class BCN_API protocol_explore bool handle_get_tx(const code& ec, interface::tx, uint8_t version, uint8_t media, const system::hash_cptr& hash, bool witness) NOEXCEPT; - bool handle_get_tx_block(const code& ec, interface::tx_block, + bool handle_get_tx_header(const code& ec, interface::tx_header, uint8_t version, uint8_t media, const system::hash_cptr& hash) NOEXCEPT; bool handle_get_tx_fee(const code& ec, interface::tx_fee, @@ -152,6 +153,7 @@ class BCN_API protocol_explore const std::optional& hash) NOEXCEPT; dispatcher dispatcher_{}; + std::atomic_bool stopping_{}; }; } // namespace node diff --git a/src/parse/target.cpp b/src/parse/target.cpp index b6e8af4b..825871cc 100644 --- a/src/parse/target.cpp +++ b/src/parse/target.cpp @@ -199,8 +199,8 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT else { const auto component = segments[segment++]; - if (component == "block") - method = "tx_block"; + if (component == "header") + method = "tx_header"; else if (component == "fee") method = "tx_fee"; else diff --git a/src/parser.cpp b/src/parser.cpp index 7a0aa1a7..6b577692 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -256,14 +256,14 @@ options_metadata parser::load_options() THROWS // Ad-hoc Testing. ( BN_READ_VARIABLE ",t", - value(&configured.test)-> - default_value(false)->zero_tokens(), + value(&configured.test)-> + default_value(system::null_hash), "Run built-in read test and display." ) ( BN_WRITE_VARIABLE ",w", - value(&configured.write)-> - default_value(false)->zero_tokens(), + value(&configured.write)-> + default_value(system::null_hash), "Run built-in write test and display." ); diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 5bdc1c17..5aa0f0fb 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,27 @@ using namespace network::messages::peer; using namespace std::placeholders; using namespace boost::json; +using point_set = std::set; +using outpoint_set = std::set; + +DEFINE_JSON_TO_TAG(point_set) +{ + point_set out{}; + for (const auto& point : value.as_array()) + out.insert(value_to(point)); + + return out; +} + +DEFINE_JSON_TO_TAG(outpoint_set) +{ + outpoint_set out{}; + for (const auto& outpoint : value.as_array()) + out.insert(value_to(outpoint)); + + return out; +} + // Avoiding namespace conflict. using object_type = network::rpc::object_t; @@ -70,7 +92,7 @@ void protocol_explore::start() NOEXCEPT SUBSCRIBE_EXPLORE(handle_get_block_tx, _1, _2, _3, _4, _5, _6, _7, _8); SUBSCRIBE_EXPLORE(handle_get_tx, _1, _2, _3, _4, _5, _6); - SUBSCRIBE_EXPLORE(handle_get_tx_block, _1, _2, _3, _4, _5); + SUBSCRIBE_EXPLORE(handle_get_tx_header, _1, _2, _3, _4, _5); SUBSCRIBE_EXPLORE(handle_get_tx_fee, _1, _2, _3, _4, _5); SUBSCRIBE_EXPLORE(handle_get_inputs, _1, _2, _3, _4, _5, _6); @@ -94,6 +116,7 @@ void protocol_explore::start() NOEXCEPT void protocol_explore::stopping(const code& ec) NOEXCEPT { BC_ASSERT(stranded()); + stopping_.store(true); dispatcher_.stop(ec); protocol_html::stopping(ec); } @@ -532,37 +555,40 @@ bool protocol_explore::handle_get_tx(const code& ec, interface::tx, uint8_t, return true; } -bool protocol_explore::handle_get_tx_block(const code& ec, interface::tx_block, +bool protocol_explore::handle_get_tx_header(const code& ec, interface::tx_header, uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT { if (stopped(ec)) return false; const auto& query = archive(); - const auto block = query.to_confirmed_block(*hash); - if (block.is_terminal()) + const auto link = query.to_confirmed_block(*hash); + if (link.is_terminal()) { send_not_found(); return true; } - const auto key = query.get_header_key(block); - if (key == null_hash) + const auto header = query.get_header(link); + if (!header) { send_internal_server_error(database::error::integrity); return true; } + constexpr auto size = chain::header::serialized_size(); switch (media) { case data: - send_chunk(to_chunk(key)); + send_chunk(to_bin(*header, size)); return true; case text: - send_text(encode_base16(key)); + send_text(to_hex(*header, size)); return true; case json: - send_json(value_from(encode_hash(key)), two * hash_size); + auto model = value_from(header); + inject(model, {}, link); + send_json(std::move(model), two * size); return true; } @@ -893,12 +919,9 @@ bool protocol_explore::handle_get_output_spenders(const code& ec, return true; } - // TODO: dedup and lexical sort. - chain::points out(points.size()); - std::ranges::transform(points, out.begin(), [&](const auto& link) NOEXCEPT - { - return query.get_spender(link); - }); + point_set out{}; + for (const auto& point: points) + out.insert(query.get_spender(point)); const auto size = out.size() * chain::point::serialized_size(); switch (media) @@ -931,8 +954,10 @@ bool protocol_explore::handle_get_address(const code& ec, interface::address, return true; } + // TODO: post queries to thread (both stopping() and this are stranded). + database::output_links outputs{}; - if (!query.to_address_outputs(outputs, *hash)) + if (!query.to_address_outputs(stopping_, outputs, *hash)) { send_internal_server_error(database::error::integrity); return true; @@ -944,14 +969,11 @@ bool protocol_explore::handle_get_address(const code& ec, interface::address, return true; } - // TODO: dedup and lexical sort. - chain::points out(outputs.size()); - std::ranges::transform(outputs, out.begin(), [&](const auto& link) NOEXCEPT - { - return query.get_spent(link); - }); + outpoint_set out{}; + for (const auto& output: outputs) + out.insert(query.get_spent(output)); - const auto size = out.size() * chain::point::serialized_size(); + const auto size = out.size() * chain::outpoint::serialized_size(); switch (media) { case data: @@ -970,41 +992,105 @@ bool protocol_explore::handle_get_address(const code& ec, interface::address, } bool protocol_explore::handle_get_address_confirmed(const code& ec, - interface::address_confirmed, uint8_t, uint8_t , - const hash_cptr& ) NOEXCEPT + interface::address_confirmed, uint8_t, uint8_t media, + const hash_cptr& hash) NOEXCEPT { if (stopped(ec)) return false; - // TODO. + const auto& query = archive(); + if (!query.address_enabled()) + { + send_not_implemented(); + return true; + } + + // TODO: post queries to thread (both stopping() and this are stranded). + + database::output_links outputs{}; + if (!query.to_confirmed_unspent_outputs(stopping_, outputs, *hash)) + { + send_internal_server_error(database::error::integrity); + return true; + } + + if (outputs.empty()) + { + send_not_found(); + return true; + } + + outpoint_set out{}; + for (const auto& output: outputs) + out.insert(query.get_spent(output)); + + const auto size = out.size() * chain::outpoint::serialized_size(); + switch (media) + { + case data: + send_chunk(to_bin_array(out, size)); + return true; + case text: + send_text(to_hex_array(out, size)); + return true; + case json: + send_json(value_from(out), two * size); + return true; + } - send_not_implemented(); + send_not_found(); return true; } bool protocol_explore::handle_get_address_unconfirmed(const code& ec, - interface::address_unconfirmed, uint8_t, uint8_t , - const hash_cptr& ) NOEXCEPT + interface::address_unconfirmed, uint8_t, uint8_t, const hash_cptr&) NOEXCEPT { if (stopped(ec)) return false; - // TODO. + // TODO: there are currently no unconfirmed txs. - send_not_implemented(); + send_not_found(); return true; } bool protocol_explore::handle_get_address_balance(const code& ec, - interface::address_balance, uint8_t, uint8_t, - const hash_cptr&) NOEXCEPT + interface::address_balance, uint8_t, uint8_t media, + const hash_cptr& hash) NOEXCEPT { if (stopped(ec)) return false; - // TODO. + const auto& query = archive(); + if (!query.address_enabled()) + { + send_not_implemented(); + return true; + } + + // TODO: post queries to thread (both stopping() and this are stranded). + + uint64_t balance{}; + if (!query.get_confirmed_balance(stopping_, balance, *hash)) + { + send_internal_server_error(database::error::integrity); + return true; + } + + switch (media) + { + case data: + send_chunk(to_little_endian_size(balance)); + return true; + case text: + send_text(encode_base16(to_little_endian_size(balance))); + return true; + case json: + send_json(balance, two * sizeof(balance)); + return true; + } - send_not_implemented(); + send_not_found(); return true; } diff --git a/test/configuration.cpp b/test/configuration.cpp index ea40c523..37b55035 100644 --- a/test/configuration.cpp +++ b/test/configuration.cpp @@ -42,8 +42,8 @@ BOOST_AUTO_TEST_CASE(configuration__construct1__none_context__expected) BOOST_REQUIRE(!instance.slabs); BOOST_REQUIRE(!instance.buckets); BOOST_REQUIRE(!instance.collisions); - BOOST_REQUIRE(!instance.test); - BOOST_REQUIRE(!instance.write); + BOOST_REQUIRE_EQUAL(instance.test, null_hash); + BOOST_REQUIRE_EQUAL(instance.write, null_hash); // Just a sample of settings. BOOST_REQUIRE(instance.node.headers_first); diff --git a/test/parse/target.cpp b/test/parse/target.cpp index 078e36ba..55be09c5 100644 --- a/test/parse/target.cpp +++ b/test/parse/target.cpp @@ -424,15 +424,15 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__tx_invalid_component__invalid_componen BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::invalid_component); } -// tx_block +// tx_header -BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_header_valid__expected) { - const std::string path = "/v42/tx/0000000000000000000000000000000000000000000000000000000000000042/block"; + const std::string path = "/v42/tx/0000000000000000000000000000000000000000000000000000000000000042/header"; request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "tx_block"); + BOOST_REQUIRE_EQUAL(request.method, "tx_header"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -452,16 +452,16 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_valid__expected) BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); } -BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_invalid_component__invalid_component) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_header_invalid_component__invalid_component) { const std::string path = "/v3/tx/0000000000000000000000000000000000000000000000000000000000000000/invalid"; request_t out{}; BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::invalid_component); } -BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_header_extra_segment__extra_segment) { - const std::string path = "/v3/tx/0000000000000000000000000000000000000000000000000000000000000000/block/extra"; + const std::string path = "/v3/tx/0000000000000000000000000000000000000000000000000000000000000000/header/extra"; request_t out{}; BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); }