Skip to content

Commit 4212a0b

Browse files
committed
feat: Support v1.6 block_headers response format with backwards compatibility
Parse `blockchain.block.headers` response based on negotiated protocol version: - v1.6+: Headers returned as array of hex strings in `headers` field - v1.4 (legacy): Headers returned as concatenated hex in `hex` field - Add `is_protocol_version_at_least()` helper for semantic version comparison - Rename old response type to `GetHeadersResLegacy` (internal) - New `GetHeadersRes` handles v1.6 format with `header_hexes` field - Both formats produce the same public `headers: Vec<block::Header>` output Co-Authored-By: Claude Code AI
1 parent 722f848 commit 4212a0b

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

src/raw_client.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ pub const PROTOCOL_VERSION_MIN: &str = "1.4";
5454
/// Maximum protocol version supported by this client.
5555
pub const PROTOCOL_VERSION_MAX: &str = "1.6";
5656

57+
/// Checks if a protocol version string is at least the specified major.minor version.
58+
fn is_protocol_version_at_least(version: &str, major: u32, minor: u32) -> bool {
59+
let mut parts = version.split('.');
60+
let v_major = parts.next().and_then(|s| s.parse::<u32>().ok());
61+
let v_minor = parts.next().and_then(|s| s.parse::<u32>().ok());
62+
match (v_major, v_minor) {
63+
(Some(v_major), Some(v_minor)) => v_major > major || (v_major == major && v_minor >= minor),
64+
_ => false,
65+
}
66+
}
67+
5768
macro_rules! impl_batch_call {
5869
( $self:expr, $data:expr, $call:ident ) => {{
5970
impl_batch_call!($self, $data, $call, )
@@ -926,16 +937,38 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
926937
);
927938
let result = self.call(req)?;
928939

929-
let mut deserialized: GetHeadersRes = serde_json::from_value(result)?;
930-
for i in 0..deserialized.count {
931-
let (start, end) = (i * 80, (i + 1) * 80);
932-
deserialized
933-
.headers
934-
.push(deserialize(&deserialized.raw_headers[start..end])?);
935-
}
936-
deserialized.raw_headers.clear();
940+
// Check protocol version to determine response format
941+
let is_v1_6_or_later = {
942+
let protocol_version = self.protocol_version.lock()?;
943+
protocol_version
944+
.as_ref()
945+
.map(|v| is_protocol_version_at_least(v, 1, 6))
946+
.unwrap_or(false)
947+
};
937948

938-
Ok(deserialized)
949+
if is_v1_6_or_later {
950+
// v1.6+: headers field contains array of hex strings
951+
let mut deserialized: GetHeadersRes = serde_json::from_value(result)?;
952+
for header_hex in &deserialized.header_hexes {
953+
let header_bytes = Vec::<u8>::from_hex(header_hex)?;
954+
deserialized.headers.push(deserialize(&header_bytes)?);
955+
}
956+
Ok(deserialized)
957+
} else {
958+
// v1.4: hex field contains concatenated headers
959+
let deserialized: GetHeadersResLegacy = serde_json::from_value(result)?;
960+
let mut headers = Vec::new();
961+
for i in 0..deserialized.count {
962+
let (start, end) = (i * 80, (i + 1) * 80);
963+
headers.push(deserialize(&deserialized.raw_headers[start..end])?);
964+
}
965+
Ok(GetHeadersRes {
966+
max: deserialized.max,
967+
count: deserialized.count,
968+
header_hexes: Vec::new(),
969+
headers,
970+
})
971+
}
939972
}
940973

941974
fn estimate_fee(&self, number: usize, mode: Option<EstimationMode>) -> Result<f64, Error> {

src/types.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,17 +273,31 @@ pub struct MempoolInfoRes {
273273
pub incrementalrelayfee: f64,
274274
}
275275

276-
/// Response to a [`block_headers`](../client/struct.Client.html#method.block_headers) request.
276+
/// Response to a [`block_headers`](../client/struct.Client.html#method.block_headers) request (protocol v1.4, legacy format).
277+
///
278+
/// In protocol v1.4, the headers are returned as a single concatenated hex string.
277279
#[derive(Clone, Debug, Deserialize)]
278-
pub struct GetHeadersRes {
280+
pub(crate) struct GetHeadersResLegacy {
279281
/// Maximum number of headers returned in a single response.
280282
pub max: usize,
281283
/// Number of headers in this response.
282284
pub count: usize,
283-
/// Raw headers concatenated. Normally cleared before returning.
285+
/// Raw headers concatenated.
284286
#[serde(rename(deserialize = "hex"), deserialize_with = "from_hex")]
285287
pub raw_headers: Vec<u8>,
286-
/// Array of block headers.
288+
}
289+
290+
/// Response to a [`block_headers`](../client/struct.Client.html#method.block_headers) request.
291+
#[derive(Clone, Debug, Deserialize)]
292+
pub struct GetHeadersRes {
293+
/// Maximum number of headers returned in a single response.
294+
pub max: usize,
295+
/// Number of headers in this response.
296+
pub count: usize,
297+
/// Array of header hex strings (v1.6 format).
298+
#[serde(default, rename(deserialize = "headers"))]
299+
pub(crate) header_hexes: Vec<String>,
300+
/// Array of block headers (populated after parsing).
287301
#[serde(skip)]
288302
pub headers: Vec<block::Header>,
289303
}

0 commit comments

Comments
 (0)