From b75b02144e85d132311edcde6dc6e8743f2c20ac Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 11 Jan 2026 21:09:21 -0500 Subject: [PATCH] Add explore interface /configuration?format=json. --- include/bitcoin/server/interfaces/explore.hpp | 66 ++++++++++--------- .../server/protocols/protocol_explore.hpp | 3 + src/parsers/explore_target.cpp | 6 +- src/protocols/protocol_explore.cpp | 31 ++++++++- test/parsers/explore_target.cpp | 56 ++++++++++++++++ 5 files changed, 128 insertions(+), 34 deletions(-) diff --git a/include/bitcoin/server/interfaces/explore.hpp b/include/bitcoin/server/interfaces/explore.hpp index dce13c7f..1c213b30 100644 --- a/include/bitcoin/server/interfaces/explore.hpp +++ b/include/bitcoin/server/interfaces/explore.hpp @@ -30,6 +30,8 @@ struct explore_methods { static constexpr std::tuple methods { + method<"configuration", uint8_t, uint8_t>{ "version", "media" }, + method<"top", uint8_t, uint8_t>{ "version", "media" }, method<"block", uint8_t, uint8_t, nullable, nullable, optional>{ "version", "media", "hash", "height", "witness" }, method<"block_header", uint8_t, uint8_t, nullable, nullable>{ "version", "media", "hash", "height" }, @@ -70,37 +72,39 @@ struct explore_methods // Derive this from above in c++26 using reflection. - using top = at<0>; - - using block = at<1>; - using block_header = at<2>; - using block_header_context = at<3>; - using block_details = at<4>; - using block_txs = at<5>; - using block_filter = at<6>; - using block_filter_hash = at<7>; - using block_filter_header = at<8>; - using block_tx = at<9>; - - using tx = at<10>; - using tx_header = at<11>; - using tx_details = at<12>; - - using inputs = at<13>; - using input = at<14>; - using input_script = at<15>; - using input_witness = at<16>; - - using outputs = at<17>; - using output = at<18>; - using output_script = at<19>; - using output_spender = at<20>; - using output_spenders = at<21>; - - using address = at<22>; - using address_confirmed = at<23>; - using address_unconfirmed = at<24>; - using address_balance = at<25>; + using configuration = at<0>; + + using top = at<1>; + + using block = at<2>; + using block_header = at<3>; + using block_header_context = at<4>; + using block_details = at<5>; + using block_txs = at<6>; + using block_filter = at<7>; + using block_filter_hash = at<8>; + using block_filter_header = at<9>; + using block_tx = at<10>; + + using tx = at<11>; + using tx_header = at<12>; + using tx_details = at<13>; + + using inputs = at<14>; + using input = at<15>; + using input_script = at<16>; + using input_witness = at<17>; + + using outputs = at<18>; + using output = at<19>; + using output_script = at<20>; + using output_spender = at<21>; + using output_spenders = at<22>; + + using address = at<23>; + using address_confirmed = at<24>; + using address_unconfirmed = at<25>; + using address_balance = at<26>; }; /// ?format=data|text|json (via query string). diff --git a/include/bitcoin/server/protocols/protocol_explore.hpp b/include/bitcoin/server/protocols/protocol_explore.hpp index 760e5674..480ea2a0 100644 --- a/include/bitcoin/server/protocols/protocol_explore.hpp +++ b/include/bitcoin/server/protocols/protocol_explore.hpp @@ -63,6 +63,9 @@ class BCS_API protocol_explore /// REST interface handlers. + bool handle_get_configuration(const code& ec, interface::configuration, + uint8_t version, uint8_t media) NOEXCEPT; + bool handle_get_top(const code& ec, interface::top, uint8_t version, uint8_t media) NOEXCEPT; diff --git a/src/parsers/explore_target.cpp b/src/parsers/explore_target.cpp index c5988271..abd398ef 100644 --- a/src/parsers/explore_target.cpp +++ b/src/parsers/explore_target.cpp @@ -84,7 +84,11 @@ code explore_target(request_t& out, const std::string_view& path) NOEXCEPT // transaction, address, inputs, and outputs are identical excluding names; // input and output are identical excluding names; block is unique. const auto target = segments[segment++]; - if (target == "top") + if (target == "configuration") + { + method = "configuration"; + } + else if (target == "top") { method = "top"; } diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 372bf091..086c561d 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -79,8 +79,9 @@ void protocol_explore::start() NOEXCEPT if (started()) return; - SUBSCRIBE_EXPLORE(handle_get_top, _1, _2, _3, _4); + SUBSCRIBE_EXPLORE(handle_get_configuration, _1, _2, _3, _4); + SUBSCRIBE_EXPLORE(handle_get_top, _1, _2, _3, _4); SUBSCRIBE_EXPLORE(handle_get_block, _1, _2, _3, _4, _5, _6, _7); SUBSCRIBE_EXPLORE(handle_get_block_header, _1, _2, _3, _4, _5, _6); SUBSCRIBE_EXPLORE(handle_get_block_header_context, _1, _2, _3, _4, _5, _6); @@ -147,7 +148,7 @@ bool protocol_explore::try_dispatch_object(const http::request& request) NOEXCEP return true; } -// Handlers. +// Serialization. // ---------------------------------------------------------------------------- constexpr auto data = to_value(http::media_type::application_octet_stream); @@ -232,6 +233,32 @@ std::string to_hex_ptr_array(const Collection& collection, size_t size, return out; } +// Handlers. +// ---------------------------------------------------------------------------- + +bool protocol_explore::handle_get_configuration(const code& ec, + interface::configuration, uint8_t, uint8_t media) NOEXCEPT +{ + if (stopped(ec)) + return false; + + if (media != json) + { + send_not_acceptable(); + return true; + } + + const auto& query = archive(); + + value model{}; + auto& object = model.emplace_object(); + object.emplace("address", query.address_enabled()); + object.emplace("filter", query.filter_enabled()); + + send_json(std::move(model), 25); + return true; +} + bool protocol_explore::handle_get_top(const code& ec, interface::top, uint8_t, uint8_t media) NOEXCEPT { diff --git a/test/parsers/explore_target.cpp b/test/parsers/explore_target.cpp index 6e0d8208..ab90b78d 100644 --- a/test/parsers/explore_target.cpp +++ b/test/parsers/explore_target.cpp @@ -63,6 +63,62 @@ BOOST_AUTO_TEST_CASE(parsers__explore_target__invalid_target__invalid_target) BOOST_REQUIRE_EQUAL(explore_target(out, "/v3/invalid"), server::error::invalid_target); } +// configuration + +BOOST_AUTO_TEST_CASE(parsers__explore_target__configuration_valid__expected) +{ + const std::string path = "/v42/configuration"; + + request_t request{}; + BOOST_REQUIRE(!explore_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "configuration"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 1u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); +} + +BOOST_AUTO_TEST_CASE(parsers__explore_target__configuration_extra_segment__extra_segment) +{ + const std::string path = "/v3/configuration/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(explore_target(out, path), server::error::extra_segment); +} + +// top + +BOOST_AUTO_TEST_CASE(parsers__explore_target__top_valid__expected) +{ + const std::string path = "/v42/top"; + + request_t request{}; + BOOST_REQUIRE(!explore_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "top"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 1u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); +} + +BOOST_AUTO_TEST_CASE(parsers__explore_target__top_extra_segment__extra_segment) +{ + const std::string path = "/v3/top/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(explore_target(out, path), server::error::extra_segment); +} + // block/height BOOST_AUTO_TEST_CASE(parsers__explore_target__block_height_valid__expected)