From 41804be81e09cee5a623efce2bd7fd1c8f81e412 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 11 May 2026 23:41:59 -0400 Subject: [PATCH 1/4] Change blockchain_block_headers to single vs. multiplicity. --- .../server/protocols/protocol_electrum.hpp | 2 +- .../electrum/protocol_electrum_headers.cpp | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp index dd5a1d28..05236111 100644 --- a/include/bitcoin/server/protocols/protocol_electrum.hpp +++ b/include/bitcoin/server/protocols/protocol_electrum.hpp @@ -237,7 +237,7 @@ class BCS_API protocol_electrum /// Common implementation for block_header/s. void blockchain_block_headers(size_t starting, size_t quantity, - size_t waypoint, bool multiplicity) NOEXCEPT; + size_t waypoint, bool single) NOEXCEPT; /// Completion handlers (for long-running address queries). /// ----------------------------------------------------------------------- diff --git a/src/protocols/electrum/protocol_electrum_headers.cpp b/src/protocols/electrum/protocol_electrum_headers.cpp index e0407b54..c5dea910 100644 --- a/src/protocols/electrum/protocol_electrum_headers.cpp +++ b/src/protocols/electrum/protocol_electrum_headers.cpp @@ -160,7 +160,7 @@ void protocol_electrum::handle_blockchain_block_header(const code& ec, return; } - blockchain_block_headers(starting, one, waypoint, false); + blockchain_block_headers(starting, one, waypoint, true); } void protocol_electrum::handle_blockchain_block_headers(const code& ec, @@ -194,12 +194,12 @@ void protocol_electrum::handle_blockchain_block_headers(const code& ec, return; } - blockchain_block_headers(starting, quantity, waypoint, true); + blockchain_block_headers(starting, quantity, waypoint, false); } // Common implementation for blockchain_block_header/s. void protocol_electrum::blockchain_block_headers(size_t starting, - size_t quantity, size_t waypoint, bool multiplicity) NOEXCEPT + size_t quantity, size_t waypoint, bool single) NOEXCEPT { const auto prove = !is_zero(quantity) && !is_zero(waypoint); const auto target = starting + sub1(quantity); @@ -237,7 +237,7 @@ void protocol_electrum::blockchain_block_headers(size_t starting, // Single header, no proof: spec requires a plain hex string result, // not a {"hex":…} or {"header":…} wrapper object. - if (!multiplicity && !prove) + if (single && !prove) { if (links.empty()) { @@ -271,7 +271,7 @@ void protocol_electrum::blockchain_block_headers(size_t starting, value_t value{ object_t{} }; auto& result = std::get(value.value()); - if (multiplicity) + if (!single) { result["max"] = maximum_headers; result["count"] = links.size(); @@ -298,10 +298,10 @@ void protocol_electrum::blockchain_block_headers(size_t starting, headers.push_back(encode_base16(header)); }; - if (multiplicity) - result["headers"] = std::move(headers); - else + if (single) result["header"] = std::move(headers.front()); + else + result["headers"] = std::move(headers); } else { @@ -317,10 +317,10 @@ void protocol_electrum::blockchain_block_headers(size_t starting, } }; - if (multiplicity) - result["hex"] = std::move(headers); - else + if (single) result["header"] = std::move(headers); + else + result["hex"] = std::move(headers); } // There is a very slim chance of inconsistency given an intervening reorg From 307bf7658e3c724a116600dee09a017c593d82b8 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 12 May 2026 01:12:23 -0400 Subject: [PATCH 2/4] Refactor blockchain_block_headers(). --- .../electrum/protocol_electrum_headers.cpp | 155 +++++++++--------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_headers.cpp b/src/protocols/electrum/protocol_electrum_headers.cpp index c5dea910..8e6657ad 100644 --- a/src/protocols/electrum/protocol_electrum_headers.cpp +++ b/src/protocols/electrum/protocol_electrum_headers.cpp @@ -235,115 +235,108 @@ void protocol_electrum::blockchain_block_headers(size_t starting, const auto links = query.get_confirmed_headers(starting, count); auto size = two * chain::header::serialized_size() * links.size(); - // Single header, no proof: spec requires a plain hex string result, - // not a {"hex":…} or {"header":…} wrapper object. + if (single && !is_one(links.size())) + { + send_code(error::server_error); + return; + } + + value_t value{}; + if (single && !prove) { - if (links.empty()) + const auto header = query.get_wire_header(links.front()); + if (header.empty()) { send_code(error::server_error); return; } + + value = encode_base16(header); + } + else + { + object_t result{}; + if (at_least(electrum::version::v1_6)) { - const auto header = query.get_wire_header(links.front()); - if (header.empty()) + // Collect headers into array. + array_t headers{}; + headers.reserve(links.size()); + for (const auto& link: links) { - send_code(error::server_error); - return; + const auto header = query.get_wire_header(link); + if (header.empty()) + { + send_code(error::server_error); + return; + } + + headers.push_back(encode_base16(header)); + } + + if (single) + { + result["header"] = std::move(headers.front()); + } + else + { + result["max"] = maximum_headers; + result["count"] = links.size(); + result["headers"] = std::move(headers); } - send_result(encode_base16(header), size + 42u); } else { - std::string header(size, '\0'); - stream::out::fast sink{ header }; + // Stream headers into single buffer. + std::string headers(size, '\0'); + stream::out::fast sink{ headers }; write::base16::fast writer{ sink }; - if (!query.get_wire_header(writer, links.front())) + for (const auto& link: links) { - send_code(error::server_error); - return; + if (!query.get_wire_header(writer, link)) + { + send_code(error::server_error); + return; + } } - send_result(std::move(header), size + 42u); - } - return; - } - - value_t value{ object_t{} }; - auto& result = std::get(value.value()); - if (!single) - { - result["max"] = maximum_headers; - result["count"] = links.size(); - } - else if (links.empty()) - { - send_code(error::server_error); - return; - } - if (at_least(electrum::version::v1_6)) - { - array_t headers{}; - headers.reserve(links.size()); - for (const auto& link: links) - { - const auto header = query.get_wire_header(link); - if (header.empty()) + if (single) { - send_code(error::server_error); - return; + result["header"] = std::move(headers); } + else + { + result["max"] = maximum_headers; + result["count"] = links.size(); + result["hex"] = std::move(headers); + } + } - headers.push_back(encode_base16(header)); - }; - - if (single) - result["header"] = std::move(headers.front()); - else - result["headers"] = std::move(headers); - } - else - { - std::string headers(size, '\0'); - stream::out::fast sink{ headers }; - write::base16::fast writer{ sink }; - for (const auto& link: links) + if (prove) { - if (!query.get_wire_header(writer, link)) + // A very slim chance of inconsistency given an intervening reorg + // because of get_merkle_root_and_proof() and height-based calcs. + // This is acceptable as must be verified by caller in any case. + hashes proof{}; + hash_digest root{}; + if (const auto code = query.get_merkle_root_and_proof(root, proof, + target, waypoint)) { - send_code(error::server_error); + send_code(code); return; } - }; - if (single) - result["header"] = std::move(headers); - else - result["hex"] = std::move(headers); - } + array_t branch(proof.size()); + std::ranges::transform(proof, branch.begin(), + [](const auto& hash) NOEXCEPT{ return encode_hash(hash); }); - // There is a very slim chance of inconsistency given an intervening reorg - // because of get_merkle_root_and_proof() use of height-based calculations. - // This is acceptable as it must be verified by caller in any case. - if (prove) - { - hashes proof{}; - hash_digest root{}; - if (const auto code = query.get_merkle_root_and_proof(root, proof, - target, waypoint)) - { - send_code(code); - return; + result["branch"] = std::move(branch); + result["root"] = encode_hash(root); + size += two * hash_size * add1(proof.size()); } - array_t branch(proof.size()); - std::ranges::transform(proof, branch.begin(), - [](const auto& hash) NOEXCEPT { return encode_hash(hash); }); - - result["branch"] = std::move(branch); - result["root"] = encode_hash(root); - size += two * hash_size * add1(proof.size()); + value = std::move(result); } send_result(std::move(value), size + 42u); From 09d00decb2273a932030aeeb5383d234f16b80b5 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 11 May 2026 23:30:19 -0400 Subject: [PATCH 3/4] Remove redundant condition (style). --- src/protocols/electrum/protocol_electrum_server.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_server.cpp b/src/protocols/electrum/protocol_electrum_server.cpp index 1d7495be..3d41b8fd 100644 --- a/src/protocols/electrum/protocol_electrum_server.cpp +++ b/src/protocols/electrum/protocol_electrum_server.cpp @@ -118,11 +118,6 @@ void protocol_electrum::handle_server_features(const code& ec, { "pruning", null_t{} } }; - if (!at_least(electrum::version::v1_7)) - { - value["hash_function"] = string_t{ "sha256" }; - } - if (at_least(electrum::version::v1_7)) { value["method_flavours"] = object_t @@ -138,6 +133,10 @@ void protocol_electrum::handle_server_features(const code& ec, } }; } + else + { + value["hash_function"] = string_t{ "sha256" }; + } send_result(std::move(value), 1024); } From 4bcb31b39f664fe52a6ae0eb5e5c198749230fab Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 12 May 2026 01:30:07 -0400 Subject: [PATCH 4/4] Fix missing ping-pong size estimate. --- src/protocols/electrum/protocol_electrum_server.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_server.cpp b/src/protocols/electrum/protocol_electrum_server.cpp index 3d41b8fd..752c3a1a 100644 --- a/src/protocols/electrum/protocol_electrum_server.cpp +++ b/src/protocols/electrum/protocol_electrum_server.cpp @@ -186,11 +186,10 @@ void protocol_electrum::handle_server_ping(const code& ec, } else { - size_t length{}; data_chunk unused{}; // Base16 encoding validation expects whole octets (even char count). - if (!to_integer(length, pong_len) || (length != data.length()) || + if (!to_integer(size, pong_len) || (size != data.length()) || !decode_base16(unused, data)) { send_code(error::invalid_argument); @@ -198,8 +197,8 @@ void protocol_electrum::handle_server_ping(const code& ec, } // Treat empty as default (args look the same, may not be correct). - if (is_nonzero(length)) - value = string_t(length, '0'); + if (is_nonzero(size)) + value = string_t(size, '0'); } // Length is limited by maximum_request (DoS protection).