Skip to content

Commit d384cf2

Browse files
committed
Merge #4: Use u32 for req ids to avoid overflow. Also add server.features method types.
7168a8c chore: bump version to 0.4 (志宇) 0957bb7 feat: Add `server.features` method types (志宇) fbd8e79 feat!: Use u32 to avoid overflow (志宇) 22755f1 chore!: Bump MSRV to 1.70 (志宇) Pull request description: Top commit has no ACKs. Tree-SHA512: 879c29929936046680bb963f611be6881155555da6106bf29ef5679df6b11fdf925c97ccc22e9060b7438092c4f3a21b14f487a9c7cfcf8618d750221ee1b6ce
2 parents ebd3da5 + 7168a8c commit d384cf2

File tree

9 files changed

+119
-16
lines changed

9 files changed

+119
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "electrum_streaming_client"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
description = "Experimental but sane electrum client by @evanlinjin."
55
license = "MIT OR Apache-2.0"
66
edition = "2021"
7-
rust-version = "1.63"
7+
rust-version = "1.70"
88
repository = "https://github.com/bitcoindevkit/electrum_streaming_client"
99
documentation = "https://docs.rs/electrum_streaming_client"
1010
readme = "README.md"

src/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl AsyncClient {
6868
let mut incoming_stream =
6969
crate::io::ReadStreamer::new(futures::io::BufReader::new(reader)).fuse();
7070
let mut state = State::<AsyncPendingRequest>::new();
71-
let mut next_id = 0_usize;
71+
let mut next_id = 0_u32;
7272

7373
let fut = async move {
7474
loop {
@@ -308,7 +308,7 @@ impl BlockingClient {
308308
Ok(())
309309
});
310310
let write_join = std::thread::spawn(move || -> std::io::Result<()> {
311-
let mut next_id = 0_usize;
311+
let mut next_id = 0_u32;
312312
for req in req_recv {
313313
let raw_req = write_state.lock().unwrap().track_request(&mut next_id, req);
314314
crate::io::blocking_write(&mut writer, raw_req)?;

src/io.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ impl<R: std::io::BufRead> Iterator for ReadStreamer<R> {
114114
if let Err(err) = reader.read_until(b'\n', &mut self.buf) {
115115
self.err = Some(err);
116116
continue;
117-
};
117+
}
118118
if self._enqueue_from_buf() {
119119
self.reader = Some(reader);
120120
}

src/lib.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub use batch_request::*;
1414
pub use hash_types::*;
1515
pub use pending_request::*;
1616
pub use request::Request;
17+
pub use serde_json;
1718
use serde_json::Value;
1819
pub use state::*;
1920
use std::fmt::Display;
@@ -183,7 +184,7 @@ pub struct RawResponse {
183184
pub version: Version,
184185

185186
/// The ID that matches the request this response is answering.
186-
pub id: usize,
187+
pub id: u32,
187188

188189
/// The result if the request succeeded, or the error object if it failed.
189190
#[serde(flatten, deserialize_with = "crate::custom_serde::result")]
@@ -214,7 +215,7 @@ pub struct RawRequest {
214215
pub jsonrpc: CowStr,
215216

216217
/// The client-assigned request ID (used to correlate with responses).
217-
pub id: usize,
218+
pub id: u32,
218219

219220
/// The method to be invoked (e.g., `"blockchain.headers.subscribe"`).
220221
pub method: CowStr,
@@ -227,14 +228,18 @@ impl RawRequest {
227228
/// Constructs a new JSON-RPC request with the given ID, method, and parameters.
228229
///
229230
/// This sets the JSON-RPC version to `"2.0"`.
230-
pub fn new(id: usize, method: CowStr, params: Vec<Value>) -> Self {
231+
pub fn new(id: u32, method: CowStr, params: Vec<Value>) -> Self {
231232
Self {
232233
jsonrpc: JSONRPC_VERSION_2_0.into(),
233234
id,
234235
method,
235236
params,
236237
}
237238
}
239+
240+
pub fn from_request<Req: Request>(id: u32, req: Req) -> Self {
241+
(id, req).into()
242+
}
238243
}
239244

240245
/// Represents either a single item or a batch of items.
@@ -275,6 +280,20 @@ impl<T> MaybeBatch<T> {
275280
}
276281
}
277282
}
283+
284+
pub fn map<T2>(self, f: impl Fn(T) -> T2) -> MaybeBatch<T2> {
285+
match self {
286+
MaybeBatch::Single(t) => MaybeBatch::Single(f(t)),
287+
MaybeBatch::Batch(items) => MaybeBatch::Batch(items.into_iter().map(f).collect()),
288+
}
289+
}
290+
291+
pub fn map_into<T2>(self) -> MaybeBatch<T2>
292+
where
293+
T: Into<T2>,
294+
{
295+
self.map(Into::into)
296+
}
278297
}
279298

280299
impl<T> From<T> for MaybeBatch<T> {

src/pending_request.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,20 @@ gen_pending_request_types! {
288288
Ping,
289289
Custom
290290
}
291+
292+
impl<A: PendingRequest> PendingRequest for Box<A> {
293+
fn to_method_and_params(&self) -> MethodAndParams {
294+
self.as_ref().to_method_and_params()
295+
}
296+
297+
fn satisfy(
298+
self,
299+
raw_resp: serde_json::Value,
300+
) -> Result<Option<SatisfiedRequest>, serde_json::Error> {
301+
(*self).satisfy(raw_resp)
302+
}
303+
304+
fn satisfy_error(self, raw_error: serde_json::Value) -> Option<ErroredRequest> {
305+
(*self).satisfy_error(raw_error)
306+
}
307+
}

src/request.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
use bitcoin::{consensus::Encodable, hex::DisplayHex, Script, Txid};
2020

2121
use crate::{
22-
response, CowStr, ElectrumScriptHash, ElectrumScriptStatus, MethodAndParams, ResponseError,
22+
response, CowStr, ElectrumScriptHash, ElectrumScriptStatus, MethodAndParams, RawRequest,
23+
ResponseError,
2324
};
2425

2526
/// A trait representing a typed Electrum JSON-RPC request.
@@ -39,6 +40,16 @@ pub trait Request: Clone {
3940
fn to_method_and_params(&self) -> MethodAndParams;
4041
}
4142

43+
impl<Req> From<(u32, Req)> for RawRequest
44+
where
45+
Req: Request,
46+
{
47+
fn from((id, req): (u32, Req)) -> Self {
48+
let (method, params) = req.to_method_and_params();
49+
RawRequest::new(id, method, params)
50+
}
51+
}
52+
4253
/// A dynamically constructed request for arbitrary Electrum methods.
4354
///
4455
/// This type allows manual specification of the method name and parameters without needing a
@@ -613,6 +624,22 @@ impl Request for Banner {
613624
}
614625
}
615626

627+
/// A request to return a list of features and services supported by the server.
628+
///
629+
/// This corresponds to the `"server.features"` Electrum RPC method.
630+
///
631+
/// See: <https://electrum-protocol.readthedocs.io/en/latest/protocol-methods.html#server-features>
632+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
633+
pub struct Features;
634+
635+
impl Request for Features {
636+
type Response = response::ServerFeatures;
637+
638+
fn to_method_and_params(&self) -> MethodAndParams {
639+
("server.features".into(), vec![])
640+
}
641+
}
642+
616643
/// A ping request to verify the connection to the Electrum server.
617644
///
618645
/// This corresponds to the `"server.ping"` Electrum RPC method. It has no parameters and returns

src/response.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
//! Electrum JSON-RPC methods. These types are used to decode responses for specific request types
55
//! defined in the [`crate::request`] module.
66
7+
use std::collections::HashMap;
8+
79
use bitcoin::{
810
absolute,
911
hashes::{Hash, HashEngine},
10-
Amount,
12+
Amount, BlockHash,
1113
};
1214

1315
use crate::DoubleSHA;
@@ -278,3 +280,41 @@ pub struct FeePair {
278280
#[serde(deserialize_with = "crate::custom_serde::weight_from_vb")]
279281
pub weight: bitcoin::Weight,
280282
}
283+
284+
/// Response to the `"server.features"` method.
285+
#[derive(Debug, Clone, serde::Deserialize)]
286+
pub struct ServerFeatures {
287+
/// Hosts.
288+
pub hosts: HashMap<String, ServerHostValues>,
289+
290+
/// The hash of the genesis block.
291+
///
292+
/// This is used to detect if a peer is connected to one serving a different network.
293+
pub genesis_hash: BlockHash,
294+
295+
/// The hash function the server uses for script hashing.
296+
///
297+
/// The default is `"sha-256"`.
298+
pub hash_function: String,
299+
300+
/// A string that identifies the server software.
301+
pub server_version: String,
302+
303+
/// The max protocol version.
304+
pub protocol_max: String,
305+
306+
/// The min protocol version.
307+
pub protocol_min: String,
308+
309+
/// The pruning limit.
310+
pub pruning: Option<u32>,
311+
}
312+
313+
/// Server host values.
314+
#[derive(Debug, Clone, serde::Deserialize)]
315+
pub struct ServerHostValues {
316+
/// SSL Port.
317+
pub ssl_port: Option<u16>,
318+
/// TCP Port.
319+
pub tcp_port: Option<u16>,
320+
}

src/state.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl Event {
6060
/// previously tracked request.
6161
#[derive(Debug)]
6262
pub struct State<PReq: PendingRequest> {
63-
pending: HashMap<usize, PReq>,
63+
pending: HashMap<u32, PReq>,
6464
}
6565

6666
impl<PReq: PendingRequest> Default for State<PReq> {
@@ -102,13 +102,13 @@ impl<PReq: PendingRequest> State<PReq> {
102102
///
103103
/// Returns a [`MaybeBatch<RawRequest>`], preserving whether the input was a single request or a
104104
/// batch.
105-
pub fn track_request<R>(&mut self, next_id: &mut usize, req: R) -> MaybeBatch<RawRequest>
105+
pub fn track_request<R>(&mut self, next_id: &mut u32, req: R) -> MaybeBatch<RawRequest>
106106
where
107107
R: Into<MaybeBatch<PReq>>,
108108
{
109109
fn _add_request<PReq: PendingRequest>(
110110
state: &mut State<PReq>,
111-
next_id: &mut usize,
111+
next_id: &mut u32,
112112
req: PReq,
113113
) -> RawRequest {
114114
let id = *next_id;
@@ -172,14 +172,14 @@ impl<PReq: PendingRequest> State<PReq> {
172172
#[derive(Debug)]
173173
pub enum ProcessError {
174174
/// A response was received for an unknown or untracked request ID.
175-
MissingRequest(usize),
175+
MissingRequest(u32),
176176

177177
/// The server returned a successful response, but it could not be deserialized into the
178178
/// expected type.
179179
///
180180
/// The `usize` is the request ID, and the `serde_json::Error` is the underlying deserialization
181181
/// failure.
182-
CannotDeserializeResponse(usize, serde_json::Error),
182+
CannotDeserializeResponse(u32, serde_json::Error),
183183

184184
/// A server notification could not be deserialized into the expected notification type.
185185
///

0 commit comments

Comments
 (0)