From ad2dc368e2f695a024c0cc6e5b61e3d92a278fe2 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 6 Dec 2025 16:54:40 -0500 Subject: [PATCH 1/5] Add chaser_validate::parallel<> helper and use common macro. --- include/bitcoin/node/chasers/chaser.hpp | 3 --- include/bitcoin/node/chasers/chaser_validate.hpp | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index 6b493cd67..af1ede51a 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -163,9 +163,6 @@ class BCN_API chaser #define SUBSCRIBE_EVENTS(method, ...) \ subscribe_events(BIND(method, __VA_ARGS__)) -#define PARALLEL(method, ...) \ - boost::asio::post(threadpool_.service(), BIND(method, __VA_ARGS__)); - } // namespace node } // namespace libbitcoin diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 5babd4457..999283bce 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -42,6 +42,14 @@ class BCN_API chaser_validate void stop() NOEXCEPT override; protected: + /// Post a method in base or derived class in parallel (use PARALLEL). + template + inline auto parallel(Method&& method, Args&&... args) NOEXCEPT + { + return boost::asio::post(threadpool_.service(), + BIND_THIS(method, args)); + } + typedef network::race_unity race; virtual bool handle_event(const code& ec, chase event_, From 0fc76a7190261313fa07cfb90f72ec6321f038c2 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 6 Dec 2025 18:48:48 -0500 Subject: [PATCH 2/5] Make address queries cancellable. --- .../node/protocols/protocol_explore.hpp | 25 +++ src/protocols/protocol_explore.cpp | 203 +++++++++++++----- 2 files changed, 170 insertions(+), 58 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp index 35eaefa79..9a95a8c7d 100644 --- a/include/bitcoin/node/protocols/protocol_explore.hpp +++ b/include/bitcoin/node/protocols/protocol_explore.hpp @@ -22,12 +22,17 @@ #include #include #include +#include #include #include namespace libbitcoin { namespace node { +// TODO: establish a place for endpoint types. +using point_set = std::set; +using outpoint_set = std::set; + class BCN_API protocol_explore : public node::protocol_html, protected network::tracker @@ -146,6 +151,26 @@ class BCN_API protocol_explore const system::hash_cptr& hash) NOEXCEPT; private: + using outpoints_cptr = std::shared_ptr; + using balance_handler = std::function; + using address_handler = std::function; + + void do_get_address(uint8_t media, const system::hash_cptr& hash, + const address_handler& handler) NOEXCEPT; + void do_get_address_confirmed(uint8_t media, const system::hash_cptr& hash, + const address_handler& handler) NOEXCEPT; + void do_get_address_unconfirmed(uint8_t media, + const system::hash_cptr& hash, + const address_handler& handler) NOEXCEPT; + + void complete_get_address(const code& ec, uint8_t media, + const outpoints_cptr& set) NOEXCEPT; + + void do_get_address_balance(uint8_t media, const system::hash_cptr& hash, + const balance_handler& handler) NOEXCEPT; + void complete_get_address_balance(const code& ec, uint8_t media, + const uint64_t balance) NOEXCEPT; + void inject(boost::json::value& out, std::optional height, const database::header_link& link) const NOEXCEPT; diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index c2882dad4..9f5afec6d 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -41,9 +40,6 @@ 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{}; @@ -941,56 +937,101 @@ bool protocol_explore::handle_get_output_spenders(const code& ec, return true; } +// handle_get_address +// ---------------------------------------------------------------------------- + bool protocol_explore::handle_get_address(const code& ec, interface::address, uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT { if (stopped(ec)) return false; - const auto& query = archive(); - if (!query.address_enabled()) + if (!archive().address_enabled()) { send_not_implemented(); return true; } - // TODO: post queries to thread (both stopping() and this are stranded). + address_handler complete = BIND(complete_get_address, _1, _2, _3); + PARALLEL(do_get_address, media, hash, std::move(complete)); + return true; +} + +// private +void protocol_explore::do_get_address(uint8_t media, const hash_cptr& hash, + const address_handler& handler) NOEXCEPT +{ + // Not stranded, query is threadsafe. + const auto& query = archive(); + // TODO: push into database as single call, generalize outpoint_set. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TODO: change query to return code to differentiate cancel vs. integrity. database::output_links outputs{}; if (!query.to_address_outputs(stopping_, outputs, *hash)) { - send_internal_server_error(database::error::integrity); - return true; + handler(network::error::operation_canceled, {}, {}); + return; } - if (outputs.empty()) + const auto set = to_shared(); + for (const auto& output: outputs) { - send_not_found(); - return true; + if (stopping_) + { + handler(network::error::operation_canceled, {}, {}); + return; + } + + set->insert(query.get_spent(output)); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - outpoint_set out{}; - for (const auto& output: outputs) - out.insert(query.get_spent(output)); + handler(network::error::success, media, set); +} + +// This is shared by the tree get_address.. methods. +void protocol_explore::complete_get_address(const code& ec, uint8_t media, + const outpoints_cptr& set) NOEXCEPT +{ + BC_ASSERT(stranded()); + + // This suppresses the error response resulting from cancelation. + if (stopped()) + return; - const auto size = out.size() * chain::outpoint::serialized_size(); + if (ec) + { + send_internal_server_error(ec); + return; + } + + if (set->empty()) + { + send_not_found(); + return; + } + + const auto size = set->size() * chain::outpoint::serialized_size(); switch (media) { case data: - send_chunk(to_bin_array(out, size)); - return true; + send_chunk(to_bin_array(*set, size)); + return; case text: - send_text(to_hex_array(out, size)); - return true; + send_text(to_hex_array(*set, size)); + return; case json: - send_json(value_from(out), two * size); - return true; + send_json(value_from(*set), two * size); + return; } send_not_found(); - return true; } +// handle_get_address_confirmed +// ---------------------------------------------------------------------------- + bool protocol_explore::handle_get_address_confirmed(const code& ec, interface::address_confirmed, uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT @@ -998,62 +1039,78 @@ bool protocol_explore::handle_get_address_confirmed(const code& ec, if (stopped(ec)) return false; - const auto& query = archive(); - if (!query.address_enabled()) + if (!archive().address_enabled()) { send_not_implemented(); return true; } - // TODO: post queries to thread (both stopping() and this are stranded). + address_handler complete = BIND(complete_get_address, _1, _2, _3); + PARALLEL(do_get_address_confirmed, media, hash, std::move(complete)); + return true; +} + +// private +void protocol_explore::do_get_address_confirmed(uint8_t media, + const hash_cptr& hash, const address_handler& handler) NOEXCEPT +{ + // Not stranded, query is threadsafe. + const auto& query = archive(); + // TODO: push into database as single call, generalize outpoint_set. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TODO: change query to return code to differentiate cancel vs. integrity. database::output_links outputs{}; if (!query.to_confirmed_unspent_outputs(stopping_, outputs, *hash)) { - send_internal_server_error(database::error::integrity); - return true; + handler(network::error::operation_canceled, {}, {}); + return; } - if (outputs.empty()) + const auto set = to_shared(); + for (const auto& output : outputs) { - send_not_found(); - return true; - } - - outpoint_set out{}; - for (const auto& output: outputs) - out.insert(query.get_spent(output)); + if (stopping_) + { + handler(network::error::operation_canceled, {}, {}); + return; + } - 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; + set->insert(query.get_spent(output)); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - send_not_found(); - return true; + handler(network::error::success, media, set); } +// handle_get_address_unconfirmed +// ---------------------------------------------------------------------------- + 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 media, + const hash_cptr& hash) NOEXCEPT { if (stopped(ec)) return false; // TODO: there are currently no unconfirmed txs. - send_not_implemented(); return true; + + address_handler complete = BIND(complete_get_address, _1, _2, _3); + PARALLEL(do_get_address_unconfirmed, media, hash, std::move(complete)); + return true; } +void protocol_explore::do_get_address_unconfirmed(uint8_t media, + const system::hash_cptr&, const address_handler& handler) NOEXCEPT +{ + handler(network::error::success, media, {}); +} + +// handle_get_address_balance +// ---------------------------------------------------------------------------- + bool protocol_explore::handle_get_address_balance(const code& ec, interface::address_balance, uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT @@ -1068,30 +1125,60 @@ bool protocol_explore::handle_get_address_balance(const code& ec, return true; } - // TODO: post queries to thread (both stopping() and this are stranded). + balance_handler complete = BIND(complete_get_address_balance, _1, _2, _3); + PARALLEL(do_get_address_balance, media, hash, std::move(complete)); + return true; +} + +void protocol_explore::do_get_address_balance(uint8_t media, + const system::hash_cptr& hash, const balance_handler& handler) NOEXCEPT +{ + // Not stranded, query is threadsafe. + const auto& query = archive(); + // TODO: push into database as single call, generalize outpoint_set. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TODO: change query to return code to differentiate cancel vs. integrity. uint64_t balance{}; if (!query.get_confirmed_balance(stopping_, balance, *hash)) { send_internal_server_error(database::error::integrity); - return true; + return; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + handler(network::error::success, media, balance); +} + +void protocol_explore::complete_get_address_balance(const code& ec, + uint8_t media, uint64_t balance) NOEXCEPT +{ + BC_ASSERT(stranded()); + + // This suppresses the error response resulting from cancelation. + if (stopped()) + return; + + if (ec) + { + send_internal_server_error(ec); + return; } switch (media) { case data: send_chunk(to_little_endian_size(balance)); - return true; + return; case text: send_text(encode_base16(to_little_endian_size(balance))); - return true; + return; case json: send_json(balance, two * sizeof(balance)); - return true; + return; } send_not_found(); - return true; } // private From e96a6fcd894c9ec802139727db005070ddeefdd1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 6 Dec 2025 18:49:16 -0500 Subject: [PATCH 3/5] Line length. --- src/protocols/protocol_explore.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 9f5afec6d..86cb7df36 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -551,8 +551,9 @@ bool protocol_explore::handle_get_tx(const code& ec, interface::tx, uint8_t, return true; } -bool protocol_explore::handle_get_tx_header(const code& ec, interface::tx_header, - uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT +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; From e33a4884054f7a225b3476207da5748bef177032 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 6 Dec 2025 19:06:46 -0500 Subject: [PATCH 4/5] Use && vs. shared_ptr for || address queries. --- .../node/protocols/protocol_explore.hpp | 5 ++-- src/protocols/protocol_explore.cpp | 24 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp index 9a95a8c7d..32507645f 100644 --- a/include/bitcoin/node/protocols/protocol_explore.hpp +++ b/include/bitcoin/node/protocols/protocol_explore.hpp @@ -151,9 +151,8 @@ class BCN_API protocol_explore const system::hash_cptr& hash) NOEXCEPT; private: - using outpoints_cptr = std::shared_ptr; using balance_handler = std::function; - using address_handler = std::function; + using address_handler = std::function; void do_get_address(uint8_t media, const system::hash_cptr& hash, const address_handler& handler) NOEXCEPT; @@ -164,7 +163,7 @@ class BCN_API protocol_explore const address_handler& handler) NOEXCEPT; void complete_get_address(const code& ec, uint8_t media, - const outpoints_cptr& set) NOEXCEPT; + const outpoint_set& set) NOEXCEPT; void do_get_address_balance(uint8_t media, const system::hash_cptr& hash, const balance_handler& handler) NOEXCEPT; diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 86cb7df36..43cea41fb 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -975,7 +975,7 @@ void protocol_explore::do_get_address(uint8_t media, const hash_cptr& hash, return; } - const auto set = to_shared(); + outpoint_set set{}; for (const auto& output: outputs) { if (stopping_) @@ -984,16 +984,16 @@ void protocol_explore::do_get_address(uint8_t media, const hash_cptr& hash, return; } - set->insert(query.get_spent(output)); + set.insert(query.get_spent(output)); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - handler(network::error::success, media, set); + handler(network::error::success, media, std::move(set)); } // This is shared by the tree get_address.. methods. void protocol_explore::complete_get_address(const code& ec, uint8_t media, - const outpoints_cptr& set) NOEXCEPT + const outpoint_set& set) NOEXCEPT { BC_ASSERT(stranded()); @@ -1007,23 +1007,23 @@ void protocol_explore::complete_get_address(const code& ec, uint8_t media, return; } - if (set->empty()) + if (set.empty()) { send_not_found(); return; } - const auto size = set->size() * chain::outpoint::serialized_size(); + const auto size = set.size() * chain::outpoint::serialized_size(); switch (media) { case data: - send_chunk(to_bin_array(*set, size)); + send_chunk(to_bin_array(set, size)); return; case text: - send_text(to_hex_array(*set, size)); + send_text(to_hex_array(set, size)); return; case json: - send_json(value_from(*set), two * size); + send_json(value_from(set), two * size); return; } @@ -1068,7 +1068,7 @@ void protocol_explore::do_get_address_confirmed(uint8_t media, return; } - const auto set = to_shared(); + outpoint_set set{}; for (const auto& output : outputs) { if (stopping_) @@ -1077,11 +1077,11 @@ void protocol_explore::do_get_address_confirmed(uint8_t media, return; } - set->insert(query.get_spent(output)); + set.insert(query.get_spent(output)); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - handler(network::error::success, media, set); + handler(network::error::success, media, std::move(set)); } // handle_get_address_unconfirmed From 90ab65bc01aef9e15664a84469a7cbacd48d6160 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 6 Dec 2025 19:46:50 -0500 Subject: [PATCH 5/5] Comments. --- src/protocols/protocol_explore.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 43cea41fb..343cbc157 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -997,7 +997,6 @@ void protocol_explore::complete_get_address(const code& ec, uint8_t media, { BC_ASSERT(stranded()); - // This suppresses the error response resulting from cancelation. if (stopped()) return; @@ -1055,7 +1054,6 @@ bool protocol_explore::handle_get_address_confirmed(const code& ec, void protocol_explore::do_get_address_confirmed(uint8_t media, const hash_cptr& hash, const address_handler& handler) NOEXCEPT { - // Not stranded, query is threadsafe. const auto& query = archive(); // TODO: push into database as single call, generalize outpoint_set. @@ -1134,10 +1132,8 @@ bool protocol_explore::handle_get_address_balance(const code& ec, void protocol_explore::do_get_address_balance(uint8_t media, const system::hash_cptr& hash, const balance_handler& handler) NOEXCEPT { - // Not stranded, query is threadsafe. const auto& query = archive(); - // TODO: push into database as single call, generalize outpoint_set. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TODO: change query to return code to differentiate cancel vs. integrity. uint64_t balance{};