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>
@@ -114,6 +115,43 @@ bool protocol_electrum::handle_event(const code&, node::chase event_,
114115 return true ;
115116}
116117
118+ // Utility.
119+ // ----------------------------------------------------------------------------
120+
121+ // TODO: move to system/math.
122+ template <typename Integer, if_integer<Integer> = true >
123+ bool to_integer (Integer& out, double value) NOEXCEPT
124+ {
125+ if (!std::isfinite (value))
126+ return false ;
127+
128+ double integral{};
129+ const double fractional = std::modf (value, &integral);
130+ if (fractional != 0.0 )
131+ return false ;
132+
133+ if (integral > static_cast <double >(system::maximum<Integer>) ||
134+ integral < static_cast <double >(system::minimum<Integer>))
135+ return false ;
136+
137+ BC_PUSH_WARNING (NO_STATIC_CAST)
138+ out = static_cast <Integer>(integral);
139+ BC_POP_WARNING ()
140+ return true ;
141+ }
142+
143+ // TODO: centralize in server (also used in bitcoind and native interfaces).
144+ template <typename Object, typename ...Args>
145+ std::string to_hex (const Object& object, size_t size, Args&&... args) NOEXCEPT
146+ {
147+ std::string out (two * size, ' \0 ' );
148+ stream::out::fast sink{ out };
149+ write::base16::fast writer{ sink };
150+ object.to_data (writer, std::forward<Args>(args)...);
151+ BC_ASSERT (writer);
152+ return out;
153+ }
154+
117155// Handlers (blockchain).
118156// ----------------------------------------------------------------------------
119157
@@ -127,11 +165,104 @@ void protocol_electrum::handle_blockchain_block_header(const code& ec,
127165
128166// electrum-protocol.readthedocs.io/en/latest/protocol-basics.html#block-headers
129167void protocol_electrum::handle_blockchain_block_headers (const code& ec,
130- rpc_interface::blockchain_block_headers, double ,
131- double , double ) NOEXCEPT
168+ rpc_interface::blockchain_block_headers, double start_height, double count ,
169+ double cp_height ) NOEXCEPT
132170{
133- if (stopped (ec)) return ;
134- send_code (error::not_implemented);
171+ using namespace system ;
172+ if (stopped (ec))
173+ return ;
174+
175+ size_t quantity{};
176+ size_t waypoint{};
177+ size_t starting{};
178+ if (!to_integer (quantity, count) ||
179+ !to_integer (waypoint, cp_height) ||
180+ !to_integer (starting, start_height))
181+ {
182+ send_code (error::invalid_argument);
183+ return ;
184+ }
185+
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));
135266}
136267
137268void protocol_electrum::handle_blockchain_headers_subscribe (const code& ec,
@@ -141,21 +272,22 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
141272 return ;
142273
143274 const auto & query = archive ();
144- const auto link = query.to_header (query.get_top_confirmed_hash ());
145- const auto height = query.get_height (link);
146- const auto header = query.get_header (link);
147- if (height.is_terminal () || !header)
275+ const auto top = query.get_top_confirmed ();
276+ const auto link = query.to_confirmed (top);
277+
278+ // This is unlikely but possible due to a race condition during reorg.
279+ if (!link.is_terminal ())
148280 {
149281 send_code (error::not_found);
150282 return ;
151283 }
152284
153- // See protocol_native::to_hex().
154- std::string hex (two * chain:: header::serialized_size (), ' \0 ' );
155- stream::out::fast sink{ hex };
156- write::base16::fast writer{ sink } ;
157- header-> to_data (writer) ;
158- BC_ASSERT (writer);
285+ const auto header = query. get_header (link);
286+ if (! header)
287+ {
288+ send_code (error::server_error) ;
289+ return ;
290+ }
159291
160292 // TODO: signal header subscription.
161293
@@ -166,8 +298,8 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
166298 {
167299 object_t
168300 {
169- { " height" , height. value },
170- { " hex" , hex }
301+ { " height" , top },
302+ { " hex" , to_hex (*header, chain::header::serialized_size ()) }
171303 }
172304 }, 256 , BIND (complete, _1));
173305}
0 commit comments