From fb9f6a88219e5bd2b680fbbb3a7e1854ac45599a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 17:49:14 -0500 Subject: [PATCH 1/9] Assert stranded in protocol_explore. --- src/protocols/protocol_explore.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index fe1b6701..5f58f93e 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -934,6 +934,8 @@ bool protocol_explore::handle_get_output_spenders(const code& ec, bool protocol_explore::handle_get_address(const code& ec, interface::address, uint8_t, uint8_t media, const hash_cptr& hash, bool turbo) NOEXCEPT { + BC_ASSERT(stranded()); + if (stopped(ec)) return false; @@ -1010,6 +1012,8 @@ bool protocol_explore::handle_get_address_confirmed(const code& ec, interface::address_confirmed, uint8_t, uint8_t media, const hash_cptr& hash, bool turbo) NOEXCEPT { + BC_ASSERT(stranded()); + if (stopped(ec)) return false; @@ -1045,6 +1049,8 @@ bool protocol_explore::handle_get_address_unconfirmed(const code& ec, interface::address_unconfirmed, uint8_t, uint8_t, const hash_cptr&, bool) NOEXCEPT { + BC_ASSERT(stranded()); + if (stopped(ec)) return false; @@ -1060,6 +1066,8 @@ bool protocol_explore::handle_get_address_balance(const code& ec, interface::address_balance, uint8_t, uint8_t media, const hash_cptr& hash, bool turbo) NOEXCEPT { + BC_ASSERT(stranded()); + if (stopped(ec)) return false; From b04e4638b584d036c78dca562d9e33f4d1c380d9 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 17:49:42 -0500 Subject: [PATCH 2/9] Change getblock default verbosity to 1. --- include/bitcoin/node/interfaces/bitcoind.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bitcoin/node/interfaces/bitcoind.hpp b/include/bitcoin/node/interfaces/bitcoind.hpp index e0f9e1bb..cc126fe7 100644 --- a/include/bitcoin/node/interfaces/bitcoind.hpp +++ b/include/bitcoin/node/interfaces/bitcoind.hpp @@ -32,7 +32,7 @@ struct bitcoind_methods { /// Blockchain methods. method<"getbestblockhash">{}, - method<"getblock", string_t, optional<0>>{ "blockhash", "verbosity" }, + method<"getblock", string_t, optional<1>>{ "blockhash", "verbosity" }, method<"getblockchaininfo">{}, method<"getblockcount">{}, method<"getblockfilter", string_t, optional<"basic"_t>>{ "blockhash", "filtertype" }, From 831edbb43c328fc25d4306f7b113e9d3283d580f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 19:45:57 -0500 Subject: [PATCH 3/9] Move origin processing to http settings (in network). --- include/bitcoin/node/protocols/protocol_html.hpp | 2 -- include/bitcoin/node/settings.hpp | 6 ------ src/protocols/protocol_html.cpp | 13 ------------- src/settings.cpp | 7 ------- test/settings.cpp | 6 ------ 5 files changed, 34 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_html.hpp b/include/bitcoin/node/protocols/protocol_html.hpp index 390ed7da..d3d52914 100644 --- a/include/bitcoin/node/protocols/protocol_html.hpp +++ b/include/bitcoin/node/protocols/protocol_html.hpp @@ -81,8 +81,6 @@ class BCN_API protocol_html const network::http::request& request={}) NOEXCEPT; /// Utilities. - bool is_allowed_origin(const network::http::fields& fields, - size_t version) const NOEXCEPT; std::filesystem::path to_path( const std::string& target = "/") const NOEXCEPT; std::filesystem::path to_local_path( diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index f9e39cf3..61a1f115 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -147,12 +147,6 @@ class BCN_API settings /// Default page for default URL (recommended). std::string default_{ "index.html" }; - /// Validated against origins if configured (recommended). - network::config::endpoints origins{}; - - /// Normalized origins. - virtual system::string_list origin_names() const NOEXCEPT; - /// !path.empty() && http_server::enabled() [hidden, not virtual] virtual bool enabled() const NOEXCEPT; }; diff --git a/src/protocols/protocol_html.cpp b/src/protocols/protocol_html.cpp index e6fc9ac0..67585b63 100644 --- a/src/protocols/protocol_html.cpp +++ b/src/protocols/protocol_html.cpp @@ -238,19 +238,6 @@ void protocol_html::send_buffer(buffer_body::value_type&& buffer, // Utilities. // ---------------------------------------------------------------------------- -bool protocol_html::is_allowed_origin(const fields& fields, - size_t version) const NOEXCEPT -{ - // Allow same-origin and no-origin requests. - // Origin header field is not available until http 1.1. - const auto origin = fields[field::origin]; - if (origin.empty() || version < version_1_1) - return true; - - return options_.origins.empty() || system::contains(options_.origins, - network::config::to_normal_host(origin, default_port())); -} - std::filesystem::path protocol_html::to_path( const std::string& target) const NOEXCEPT { diff --git a/src/settings.cpp b/src/settings.cpp index bef10a88..0d8f03c1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -180,13 +180,6 @@ settings::html_server::html_server(const std::string_view& logging_name, { } -system::string_list settings::html_server::origin_names() const NOEXCEPT -{ - // secure changes default port from 80 to 443. - const auto port = secure ? http::default_tls : http::default_http; - return network::config::to_host_names(hosts, port); -} - bool settings::html_server::enabled() const NOEXCEPT { return (!path.empty() || pages.enabled()) && http_server::enabled(); diff --git a/test/settings.cpp b/test/settings.cpp index 153427e2..459f1455 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -115,8 +115,6 @@ BOOST_AUTO_TEST_CASE(server__html_server__defaults__expected) BOOST_REQUIRE(instance.websocket); BOOST_REQUIRE(instance.path.empty()); BOOST_REQUIRE_EQUAL(instance.default_, "index.html"); - BOOST_REQUIRE(instance.origins.empty()); - BOOST_REQUIRE(instance.origin_names().empty()); } BOOST_AUTO_TEST_CASE(server__web_server__defaults__expected) @@ -152,8 +150,6 @@ BOOST_AUTO_TEST_CASE(server__web_server__defaults__expected) BOOST_REQUIRE(server.path.empty()); BOOST_REQUIRE(server.websocket); BOOST_REQUIRE_EQUAL(server.default_, "index.html"); - BOOST_REQUIRE(server.origins.empty()); - BOOST_REQUIRE(server.origin_names().empty()); } BOOST_AUTO_TEST_CASE(server__explore_server__defaults__expected) @@ -189,8 +185,6 @@ BOOST_AUTO_TEST_CASE(server__explore_server__defaults__expected) BOOST_REQUIRE(server.path.empty()); BOOST_REQUIRE(server.websocket); BOOST_REQUIRE_EQUAL(server.default_, "index.html"); - BOOST_REQUIRE(server.origins.empty()); - BOOST_REQUIRE(server.origin_names().empty()); } // TODO: could add websocket under bitcoind as a custom property. From c846c40a281f83915123db7f9f55978db3ff75a9 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 19:46:22 -0500 Subject: [PATCH 4/9] Add web/explore.allow_opaque_origin bool setting parse. --- src/parser.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index fc547e46..788e733c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -800,7 +800,12 @@ options_metadata parser::load_settings() THROWS ( "web.origin", value(&configured.server.web.origins), - "The allowed origin (http verification), multiple allowed, defaults to empty (disabled)." + "The allowed origin (see CORS), multiple allowed, defaults to empty (disabled)." + ) + ( + "web.allow_opaque_origin", + value(&configured.server.web.allow_opaque_origin), + "Allow requests from opaue origin (see CORS), multiple allowed, defaults to true." ) ( "web.path", @@ -852,7 +857,12 @@ options_metadata parser::load_settings() THROWS ( "explore.origin", value(&configured.server.explore.origins), - "The allowed origin (http verification), multiple allowed, defaults to empty (disabled)." + "The allowed origin (see CORS), multiple allowed, defaults to empty (disabled)." + ) + ( + "explore.allow_opaque_origin", + value(&configured.server.explore.allow_opaque_origin), + "Allow requests from opaue origin (see CORS), multiple allowed, defaults to true." ) ( "explore.path", From 853439cab7e983493a62c7873a6788d6c6ea206e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 19:48:14 -0500 Subject: [PATCH 5/9] Add protocol_bitcoind::send_json and handle_receive_options. --- .../node/protocols/protocol_bitcoind.hpp | 5 ++ src/protocols/protocol_bitcoind.cpp | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_bitcoind.hpp b/include/bitcoin/node/protocols/protocol_bitcoind.hpp index 3fab06e5..35f81987 100644 --- a/include/bitcoin/node/protocols/protocol_bitcoind.hpp +++ b/include/bitcoin/node/protocols/protocol_bitcoind.hpp @@ -56,6 +56,8 @@ class BCN_API protocol_bitcoind } /// Dispatch. + void handle_receive_options(const code& ec, + const network::http::method::options::cptr& options) NOEXCEPT override; void handle_receive_post(const code& ec, const network::http::method::post::cptr& post) NOEXCEPT override; @@ -99,6 +101,9 @@ class BCN_API protocol_bitcoind interface::verify_tx_out_set, const std::string&) NOEXCEPT; private: + void send_json(boost::json::value&& model, size_t size_hint, + const network::http::request& request={}) NOEXCEPT; + // This is thread safe. ////const options_t& options_; diff --git a/src/protocols/protocol_bitcoind.cpp b/src/protocols/protocol_bitcoind.cpp index b7cf1609..77b105ff 100644 --- a/src/protocols/protocol_bitcoind.cpp +++ b/src/protocols/protocol_bitcoind.cpp @@ -34,8 +34,7 @@ using namespace network::http; using namespace std::placeholders; using namespace boost::json; -using json_t = json_body::value_type; - +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) @@ -74,6 +73,32 @@ void protocol_bitcoind::start() NOEXCEPT // Dispatch. // ---------------------------------------------------------------------------- +void protocol_bitcoind::handle_receive_options(const code& ec, + const network::http::method::options::cptr& options) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (stopped(ec)) + return; + + // Enforce http host header (if any hosts are configured). + if (!is_allowed_host(*options, options->version())) + { + send_bad_host(*options); + return; + } + + // Enforce http origin policy (if any origins are configured). + if (!is_allowed_origin(*options, options->version())) + { + send_forbidden(*options); + return; + } + + send_ok(*options); +} + +// TODO: also handle_receive_get and dispatch based on URL parse. void protocol_bitcoind::handle_receive_post(const code& ec, const network::http::method::post::cptr& post) NOEXCEPT { @@ -82,6 +107,21 @@ void protocol_bitcoind::handle_receive_post(const code& ec, if (stopped(ec)) return; + // Enforce http host header (if any hosts are configured). + if (!is_allowed_host(*post, post->version())) + { + send_bad_host(*post); + return; + } + + // Enforce http origin policy (if any origins are configured). + if (!is_allowed_origin(*post, post->version())) + { + send_forbidden(*post); + return; + } + + using json_t = json_body::value_type; const auto& body = post->body(); if (!body.contains()) { @@ -228,6 +268,24 @@ bool protocol_bitcoind::handle_verify_tx_out_set(const code& ec, return !ec; } +// private +// ---------------------------------------------------------------------------- + +// TODO: post-process response for json-rpc version. +void protocol_bitcoind::send_json(boost::json::value&& model, size_t size_hint, + const request& request) NOEXCEPT +{ + BC_ASSERT(stranded()); + constexpr auto json = media_type::application_json; + response response{ status::ok, request.version() }; + add_common_headers(response, request); + response.set(field::content_type, from_media_type(json)); + response.body() = { std::move(model), size_hint }; + response.prepare_payload(); + SEND(std::move(response), handle_complete, _1, error::success); +} + +BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() From 94aed74201ce9ab9d86850367c7752ee3d0a4a49 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 20:48:13 -0500 Subject: [PATCH 6/9] Change allow_opaque_origin to false default. --- src/parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 788e733c..4a39b340 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -805,7 +805,7 @@ options_metadata parser::load_settings() THROWS ( "web.allow_opaque_origin", value(&configured.server.web.allow_opaque_origin), - "Allow requests from opaue origin (see CORS), multiple allowed, defaults to true." + "Allow requests from opaue origin (see CORS), multiple allowed, defaults to false." ) ( "web.path", @@ -862,7 +862,7 @@ options_metadata parser::load_settings() THROWS ( "explore.allow_opaque_origin", value(&configured.server.explore.allow_opaque_origin), - "Allow requests from opaue origin (see CORS), multiple allowed, defaults to true." + "Allow requests from opaue origin (see CORS), multiple allowed, defaults to false." ) ( "explore.path", From e21307ebab92e29cc0ec32098a67683c7d25ed3d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 20:48:43 -0500 Subject: [PATCH 7/9] Implement protocol_bitcoind::handle_get_best_block_hash(). --- src/protocols/protocol_bitcoind.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/protocols/protocol_bitcoind.cpp b/src/protocols/protocol_bitcoind.cpp index 77b105ff..7536e295 100644 --- a/src/protocols/protocol_bitcoind.cpp +++ b/src/protocols/protocol_bitcoind.cpp @@ -146,17 +146,29 @@ void protocol_bitcoind::handle_receive_post(const code& ec, } // TODO: post-process request. + // github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md if (const auto code = dispatcher_.notify(request)) stop(code); } // Handlers. // ---------------------------------------------------------------------------- +// TODO: precompute size for buffer hints. +// {"jsonrpc": "1.0", "id": "curltest", "method": "getbestblockhash", "params": []} bool protocol_bitcoind::handle_get_best_block_hash(const code& ec, interface::get_best_block_hash) NOEXCEPT { - return !ec; + if (stopped(ec)) + return false; + + const auto& query = archive(); + const auto hash = query.get_header_key(query.to_confirmed( + query.get_top_confirmed())); + + const response_t model{ .result = encode_hash(hash) }; + send_json(value_from(model), two * system::hash_size); + return true; } // method<"getblock", string_t, optional<0_u32>>{ "blockhash", "verbosity" }, From aea290fa75ecbe241fde207f3d69b3c26dd65237 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 13 Dec 2025 21:01:58 -0500 Subject: [PATCH 8/9] Add origin config parse to bitcoind. --- src/parser.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index 4a39b340..ccd20fdd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -916,6 +916,16 @@ options_metadata parser::load_settings() THROWS value(&configured.server.bitcoind.hosts), "The host name (http verification), multiple allowed, defaults to empty (disabled)." ) + ( + "bitcoind.origin", + value(&configured.server.bitcoind.origins), + "The allowed origin (see CORS), multiple allowed, defaults to empty (disabled)." + ) + ( + "bitcoind.allow_opaque_origin", + value(&configured.server.bitcoind.allow_opaque_origin), + "Allow requests from opaue origin (see CORS), multiple allowed, defaults to false." + ) /* [electrum] */ ////( From 074e8751685ffd35aa471715e65e0d0348825125 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 14 Dec 2025 03:50:05 -0500 Subject: [PATCH 9/9] Include CORS headers in bitcoind POST response as required. --- .../node/protocols/protocol_bitcoind.hpp | 16 +++++++--- src/protocols/protocol_bitcoind.cpp | 30 ++++++++++++++----- src/protocols/protocol_html.cpp | 6 ++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_bitcoind.hpp b/include/bitcoin/node/protocols/protocol_bitcoind.hpp index 35f81987..08b8034e 100644 --- a/include/bitcoin/node/protocols/protocol_bitcoind.hpp +++ b/include/bitcoin/node/protocols/protocol_bitcoind.hpp @@ -49,6 +49,9 @@ class BCN_API protocol_bitcoind void start() NOEXCEPT override; protected: + using post = network::http::method::post; + using options = network::http::method::options; + template inline void subscribe(Method&& method, Args&&... args) NOEXCEPT { @@ -59,7 +62,7 @@ class BCN_API protocol_bitcoind void handle_receive_options(const code& ec, const network::http::method::options::cptr& options) NOEXCEPT override; void handle_receive_post(const code& ec, - const network::http::method::post::cptr& post) NOEXCEPT override; + const post::cptr& post) NOEXCEPT override; /// Handlers. bool handle_get_best_block_hash(const code& ec, @@ -101,14 +104,19 @@ class BCN_API protocol_bitcoind interface::verify_tx_out_set, const std::string&) NOEXCEPT; private: - void send_json(boost::json::value&& model, size_t size_hint, - const network::http::request& request={}) NOEXCEPT; + // Provide the request for serialization, keeping it out of dispatch. + void set_post(const post::cptr& post) NOEXCEPT; + const post& get_post() const NOEXCEPT; + + // Send the response. + void send_json(boost::json::value&& model, size_t size_hint) NOEXCEPT; // This is thread safe. ////const options_t& options_; - // This is protected by strand. + // These are protected by strand. dispatcher dispatcher_{}; + post::cptr post_{}; }; } // namespace node diff --git a/src/protocols/protocol_bitcoind.cpp b/src/protocols/protocol_bitcoind.cpp index 7536e295..c7c62c44 100644 --- a/src/protocols/protocol_bitcoind.cpp +++ b/src/protocols/protocol_bitcoind.cpp @@ -74,7 +74,7 @@ void protocol_bitcoind::start() NOEXCEPT // ---------------------------------------------------------------------------- void protocol_bitcoind::handle_receive_options(const code& ec, - const network::http::method::options::cptr& options) NOEXCEPT + const options::cptr& options) NOEXCEPT { BC_ASSERT(stranded()); @@ -100,7 +100,7 @@ void protocol_bitcoind::handle_receive_options(const code& ec, // TODO: also handle_receive_get and dispatch based on URL parse. void protocol_bitcoind::handle_receive_post(const code& ec, - const network::http::method::post::cptr& post) NOEXCEPT + const post::cptr& post) NOEXCEPT { BC_ASSERT(stranded()); @@ -145,14 +145,14 @@ void protocol_bitcoind::handle_receive_post(const code& ec, return; } - // TODO: post-process request. - // github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md + set_post(post); if (const auto code = dispatcher_.notify(request)) stop(code); } // Handlers. // ---------------------------------------------------------------------------- +// github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md // TODO: precompute size for buffer hints. // {"jsonrpc": "1.0", "id": "curltest", "method": "getbestblockhash", "params": []} @@ -283,14 +283,28 @@ bool protocol_bitcoind::handle_verify_tx_out_set(const code& ec, // private // ---------------------------------------------------------------------------- +void protocol_bitcoind::set_post(const post::cptr& post) NOEXCEPT +{ + BC_ASSERT(post); + post_ = post; +} + +const protocol_bitcoind::post& protocol_bitcoind::get_post() const NOEXCEPT +{ + BC_ASSERT(post_); + return *post_; +} + // TODO: post-process response for json-rpc version. -void protocol_bitcoind::send_json(boost::json::value&& model, size_t size_hint, - const request& request) NOEXCEPT +void protocol_bitcoind::send_json(boost::json::value&& model, + size_t size_hint) NOEXCEPT { BC_ASSERT(stranded()); + const auto& post = get_post(); constexpr auto json = media_type::application_json; - response response{ status::ok, request.version() }; - add_common_headers(response, request); + response response{ status::ok, post.version() }; + add_common_headers(response, post); + add_access_control_headers(response, post); response.set(field::content_type, from_media_type(json)); response.body() = { std::move(model), size_hint }; response.prepare_payload(); diff --git a/src/protocols/protocol_html.cpp b/src/protocols/protocol_html.cpp index 67585b63..268a26e8 100644 --- a/src/protocols/protocol_html.cpp +++ b/src/protocols/protocol_html.cpp @@ -168,6 +168,7 @@ void protocol_html::send_json(boost::json::value&& model, size_t size_hint, BC_ASSERT(stranded()); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(json)); response.body() = { std::move(model), size_hint }; response.prepare_payload(); @@ -180,6 +181,7 @@ void protocol_html::send_text(std::string&& hexidecimal, BC_ASSERT(stranded()); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(text)); response.body() = std::move(hexidecimal); response.prepare_payload(); @@ -192,6 +194,7 @@ void protocol_html::send_chunk(system::data_chunk&& bytes, BC_ASSERT(stranded()); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(data)); response.body() = std::move(bytes); response.prepare_payload(); @@ -205,6 +208,7 @@ void protocol_html::send_file(file&& file, media_type type, BC_ASSERT_MSG(file.is_open(), "sending closed file handle"); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(type)); response.body() = std::move(file); response.prepare_payload(); @@ -217,6 +221,7 @@ void protocol_html::send_span(span_body::value_type&& span, BC_ASSERT(stranded()); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(type)); response.body() = std::move(span); response.prepare_payload(); @@ -229,6 +234,7 @@ void protocol_html::send_buffer(buffer_body::value_type&& buffer, BC_ASSERT(stranded()); response response{ status::ok, request.version() }; add_common_headers(response, request); + add_access_control_headers(response, request); response.set(field::content_type, from_media_type(type)); response.body() = std::move(buffer); response.prepare_payload();