1919#include < bitcoin/server/protocols/protocol_electrum.hpp>
2020
2121#include < algorithm>
22+ #include < ranges>
2223#include < variant>
2324#include < bitcoin/server/define.hpp>
2425#include < bitcoin/server/interfaces/interfaces.hpp>
@@ -167,6 +168,7 @@ void protocol_electrum::handle_blockchain_block_headers(const code& ec,
167168 rpc_interface::blockchain_block_headers, double start_height, double count,
168169 double cp_height) NOEXCEPT
169170{
171+ using namespace system ;
170172 if (stopped (ec))
171173 return ;
172174
@@ -181,7 +183,86 @@ void protocol_electrum::handle_blockchain_block_headers(const code& ec,
181183 return ;
182184 }
183185
184- send_code (error::not_implemented);
186+ if (is_add_overflow (starting, quantity))
187+ {
188+ send_code (error::argument_overflow);
189+ return ;
190+ }
191+
192+ // The documented requirement: `start_height + (count - 1) <= cp_height` is
193+ // ambiguous at count = 0 so guard must be applied to both args and prover.
194+ const auto target = starting + sub1 (quantity);
195+ const auto prove = !is_zero (quantity) && !is_zero (waypoint);
196+ if (prove && target > waypoint)
197+ {
198+ send_code (error::target_overflow);
199+ return ;
200+ }
201+
202+ // Recommended to be at least one difficulty retarget period, e.g. 2016.
203+ // The maximum number of headers the server will return in single request.
204+ const auto maximum = server_settings ().electrum .maximum_headers ;
205+
206+ // Returned headers are assured to be contiguous despite intervening reorg.
207+ // No headers may be returned, which implies start > confirmed top block.
208+ const auto & query = archive ();
209+ const auto bound = limit (quantity, maximum);
210+ const auto links = query.get_confirmed_headers (starting, bound);
211+ constexpr auto header_size = chain::header::serialized_size ();
212+ auto size = two * header_size * links.size ();
213+
214+ // Fetch and serialize headers.
215+ array_t headers{};
216+ headers.reserve (links.size ());
217+ for (const auto & link: links)
218+ {
219+ if (const auto header = query.get_header (link); header)
220+ {
221+ // TODO: optimize by query directly returning wire serialization.
222+ headers.push_back (to_hex (*header, header_size));
223+ }
224+ else
225+ {
226+ send_code (error::server_error);
227+ return ;
228+ }
229+ };
230+
231+ value_t value
232+ {
233+ object_t
234+ {
235+ { " count" , quantity },
236+ { " headers" , std::move (headers) },
237+ { " max" , maximum }
238+ }
239+ };
240+
241+ // There is a very slim change of inconsistency given an intervening reorg
242+ // because of get_merkle_root_and_proof() use of height-based calculations.
243+ // This is acceptable as it must be verified by caller in any case.
244+ if (prove)
245+ {
246+ hashes proof{};
247+ hash_digest root{};
248+ if (const auto code = query.get_merkle_root_and_proof (root, proof,
249+ target, waypoint))
250+ {
251+ send_code (code);
252+ return ;
253+ }
254+
255+ array_t branch (proof.size ());
256+ std::ranges::transform (proof, branch.begin (),
257+ [](const auto & hash) { return encode_base16 (hash); });
258+
259+ auto & result = std::get<object_t >(value.value ());
260+ result[" root" ] = encode_base16 (root);
261+ result[" branch" ] = std::move (branch);
262+ size += two * hash_size * add1 (proof.size ());
263+ }
264+
265+ send_result (std::move (value), size + 42u , BIND (complete, _1));
185266}
186267
187268void protocol_electrum::handle_blockchain_headers_subscribe (const code& ec,
0 commit comments