@@ -45,6 +45,15 @@ use crate::api::ElectrumApi;
4545use crate :: batch:: Batch ;
4646use crate :: types:: * ;
4747
48+ /// Client name sent to the server during protocol version negotiation.
49+ pub const CLIENT_NAME : & str = "rust-electrum-client" ;
50+
51+ /// Minimum protocol version supported by this client.
52+ pub const PROTOCOL_VERSION_MIN : & str = "1.4" ;
53+
54+ /// Maximum protocol version supported by this client.
55+ pub const PROTOCOL_VERSION_MAX : & str = "1.6" ;
56+
4857macro_rules! impl_batch_call {
4958 ( $self: expr, $data: expr, $call: ident ) => { {
5059 impl_batch_call!( $self, $data, $call, )
@@ -142,6 +151,9 @@ where
142151 headers : Mutex < VecDeque < RawHeaderNotification > > ,
143152 script_notifications : Mutex < HashMap < ScriptHash , VecDeque < ScriptStatus > > > ,
144153
154+ /// The protocol version negotiated with the server via `server.version`.
155+ protocol_version : Mutex < Option < String > > ,
156+
145157 #[ cfg( feature = "debug-calls" ) ]
146158 calls : AtomicUsize ,
147159}
@@ -163,6 +175,8 @@ where
163175 headers : Mutex :: new ( VecDeque :: new ( ) ) ,
164176 script_notifications : Mutex :: new ( HashMap :: new ( ) ) ,
165177
178+ protocol_version : Mutex :: new ( None ) ,
179+
166180 #[ cfg( feature = "debug-calls" ) ]
167181 calls : AtomicUsize :: new ( 0 ) ,
168182 }
@@ -173,6 +187,9 @@ where
173187pub type ElectrumPlaintextStream = TcpStream ;
174188impl RawClient < ElectrumPlaintextStream > {
175189 /// Creates a new plaintext client and tries to connect to `socket_addr`.
190+ ///
191+ /// Automatically negotiates the protocol version with the server using
192+ /// `server.version` as required by the Electrum protocol.
176193 pub fn new < A : ToSocketAddrs > (
177194 socket_addrs : A ,
178195 timeout : Option < Duration > ,
@@ -187,7 +204,9 @@ impl RawClient<ElectrumPlaintextStream> {
187204 None => TcpStream :: connect ( socket_addrs) ?,
188205 } ;
189206
190- Ok ( stream. into ( ) )
207+ let client: Self = stream. into ( ) ;
208+ client. negotiate_protocol_version ( ) ?;
209+ Ok ( client)
191210 }
192211}
193212
@@ -285,7 +304,9 @@ impl RawClient<ElectrumSslStream> {
285304 . connect ( & domain, stream)
286305 . map_err ( Error :: SslHandshakeError ) ?;
287306
288- Ok ( stream. into ( ) )
307+ let client: Self = stream. into ( ) ;
308+ client. negotiate_protocol_version ( ) ?;
309+ Ok ( client)
289310 }
290311}
291312
@@ -466,7 +487,9 @@ impl RawClient<ElectrumSslStream> {
466487 . map_err ( Error :: CouldNotCreateConnection ) ?;
467488 let stream = StreamOwned :: new ( session, tcp_stream) ;
468489
469- Ok ( stream. into ( ) )
490+ let client: Self = stream. into ( ) ;
491+ client. negotiate_protocol_version ( ) ?;
492+ Ok ( client)
470493 }
471494}
472495
@@ -496,7 +519,9 @@ impl RawClient<ElectrumProxyStream> {
496519 stream. get_mut ( ) . set_read_timeout ( timeout) ?;
497520 stream. get_mut ( ) . set_write_timeout ( timeout) ?;
498521
499- Ok ( stream. into ( ) )
522+ let client: Self = stream. into ( ) ;
523+ client. negotiate_protocol_version ( ) ?;
524+ Ok ( client)
500525 }
501526
502527 #[ cfg( any(
@@ -551,6 +576,35 @@ impl<S: Read + Write> RawClient<S> {
551576 // self._reader_thread(None).map(|_| ())
552577 // }
553578
579+ /// Negotiates the protocol version with the server.
580+ ///
581+ /// This sends `server.version` as the first message and stores the negotiated
582+ /// protocol version. Called automatically by constructors.
583+ fn negotiate_protocol_version ( & self ) -> Result < ( ) , Error > {
584+ let version_range = vec ! [
585+ PROTOCOL_VERSION_MIN . to_string( ) ,
586+ PROTOCOL_VERSION_MAX . to_string( ) ,
587+ ] ;
588+ let req = Request :: new_id (
589+ self . last_id . fetch_add ( 1 , Ordering :: SeqCst ) ,
590+ "server.version" ,
591+ vec ! [
592+ Param :: String ( CLIENT_NAME . to_string( ) ) ,
593+ Param :: StringVec ( version_range) ,
594+ ] ,
595+ ) ;
596+ let result = self . call ( req) ?;
597+ let response: ServerVersionRes = serde_json:: from_value ( result) ?;
598+
599+ * self . protocol_version . lock ( ) ? = Some ( response. protocol_version ) ;
600+ Ok ( ( ) )
601+ }
602+
603+ /// Returns the protocol version negotiated with the server, if available.
604+ pub fn protocol_version ( & self ) -> Result < Option < String > , Error > {
605+ Ok ( self . protocol_version . lock ( ) ?. clone ( ) )
606+ }
607+
554608 fn _reader_thread ( & self , until_message : Option < usize > ) -> Result < serde_json:: Value , Error > {
555609 let mut raw_resp = String :: new ( ) ;
556610 let resp = match self . buf_reader . try_lock ( ) {
@@ -1220,8 +1274,12 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
12201274 ] ,
12211275 ) ;
12221276 let result = self . call ( req) ?;
1277+ let response: ServerVersionRes = serde_json:: from_value ( result) ?;
12231278
1224- Ok ( serde_json:: from_value ( result) ?)
1279+ // Store the negotiated protocol version
1280+ * self . protocol_version . lock ( ) ? = Some ( response. protocol_version . clone ( ) ) ;
1281+
1282+ Ok ( response)
12251283 }
12261284
12271285 fn ping ( & self ) -> Result < ( ) , Error > {
0 commit comments