1
+ //! Electrum client
2
+ //!
3
+ //! This module contains definitions of all the complex data structures that are returned by calls
4
+
1
5
use std:: collections:: { BTreeMap , VecDeque } ;
2
6
#[ cfg( test) ]
3
7
use std:: fs:: File ;
@@ -55,6 +59,20 @@ macro_rules! impl_batch_call {
55
59
} } ;
56
60
}
57
61
62
+ /// Instance of an Electrum client
63
+ ///
64
+ /// A `Client` maintains a constant connection with an Electrum server and exposes methods to
65
+ /// interact with it. It can also subscribe and receive notifictations from the server about new
66
+ /// blocks or activity on a specific *scriptPubKey*.
67
+ ///
68
+ /// The `Client` is modeled in such a way that allows the external caller to have full control over
69
+ /// its functionality: no threads or tasks are spawned internally to monitor the state of the
70
+ /// connection. This allows the caller to control its behavior through some *polling* functions,
71
+ /// and ultimately makes the library more lightweight and easier to embed into existing
72
+ /// projects.
73
+ ///
74
+ /// More transport methods can be used by manually creating an instance of this struct with an
75
+ /// arbitray `S` type.
58
76
#[ derive( Debug ) ]
59
77
pub struct Client < S >
60
78
where
70
88
calls : usize ,
71
89
}
72
90
73
- impl Client < TcpStream > {
74
- pub fn new < A : ToSocketAddrs > ( socket_addr : A ) -> io:: Result < Self > {
91
+ /// Transport type used to establish a plaintext TCP connection with the server
92
+ pub type ElectrumPlaintextStream = TcpStream ;
93
+ impl Client < ElectrumPlaintextStream > {
94
+ /// Creates a new plaintext client and tries to connect to `socket_addr`.
95
+ pub fn new < A : ToSocketAddrs > ( socket_addr : A ) -> Result < Self , Error > {
75
96
let stream = TcpStream :: connect ( socket_addr) ?;
76
97
let buf_reader = BufReader :: new ( stream. try_clone ( ) ?) ;
77
98
@@ -88,7 +109,12 @@ impl Client<TcpStream> {
88
109
}
89
110
90
111
#[ cfg( feature = "use-openssl" ) ]
91
- impl Client < ClonableStream < SslStream < TcpStream > > > {
112
+ /// Transport type used to establish an OpenSSL TLS encrypted/authenticated connection with the server
113
+ pub type ElectrumSslStream = ClonableStream < SslStream < TcpStream > > ;
114
+ #[ cfg( feature = "use-openssl" ) ]
115
+ impl Client < ElectrumSslStream > {
116
+ /// Creates a new SSL client and tries to connect to `socket_addr`. Optionally, if `domain` is not
117
+ /// None, validates the server certificate.
92
118
pub fn new_ssl < A : ToSocketAddrs > ( socket_addr : A , domain : Option < & str > ) -> Result < Self , Error > {
93
119
let mut builder =
94
120
SslConnector :: builder ( SslMethod :: tls ( ) ) . map_err ( Error :: InvalidSslMethod ) ?;
@@ -145,7 +171,15 @@ mod danger {
145
171
any( feature = "default" , feature = "use-rustls" ) ,
146
172
not( feature = "use-openssl" )
147
173
) ) ]
148
- impl Client < ClonableStream < StreamOwned < ClientSession , TcpStream > > > {
174
+ /// Transport type used to establish a Rustls TLS encrypted/authenticated connection with the server
175
+ pub type ElectrumSslStream = ClonableStream < StreamOwned < ClientSession , TcpStream > > ;
176
+ #[ cfg( all(
177
+ any( feature = "default" , feature = "use-rustls" ) ,
178
+ not( feature = "use-openssl" )
179
+ ) ) ]
180
+ impl Client < ElectrumSslStream > {
181
+ /// Creates a new SSL client and tries to connect to `socket_addr`. Optionally, if `domain` is not
182
+ /// None, validates the server certificate against `webpki-root`'s list of Certificate Authorities.
149
183
pub fn new_ssl < A : ToSocketAddrs > ( socket_addr : A , domain : Option < & str > ) -> Result < Self , Error > {
150
184
let mut config = ClientConfig :: new ( ) ;
151
185
if domain. is_none ( ) {
@@ -183,7 +217,13 @@ impl Client<ClonableStream<StreamOwned<ClientSession, TcpStream>>> {
183
217
}
184
218
185
219
#[ cfg( any( feature = "default" , feature = "proxy" ) ) ]
186
- impl Client < ClonableStream < Socks5Stream > > {
220
+ /// Transport type used to establish a connection to a server through a socks proxy
221
+ pub type ElectrumProxyStream = ClonableStream < Socks5Stream > ;
222
+ #[ cfg( any( feature = "default" , feature = "proxy" ) ) ]
223
+ impl Client < ElectrumProxyStream > {
224
+ /// Creates a new socks client and tries to connect to `target_addr` using `proxy_addr` as an
225
+ /// unauthenticated socks proxy server. The DNS resolution of `target_addr`, if required, is done
226
+ /// through the proxy. This allows to specify, for instance, `.onion` addresses.
187
227
pub fn new_proxy < A : ToSocketAddrs , T : ToTargetAddr > (
188
228
target_addr : T ,
189
229
proxy_addr : A ,
@@ -250,6 +290,9 @@ impl<S: Read + Write> Client<S> {
250
290
Ok ( resp[ "result" ] . take ( ) )
251
291
}
252
292
293
+ /// Execute a queue of calls stored in a [`Batch`](../batch/struct.Batch.html) struct. Returns
294
+ /// `Ok()` **only if** all of the calls are successful. The order of the JSON `Value`s returned
295
+ /// reflects the order in which the calls were made on the `Batch` struct.
253
296
pub fn batch_call ( & mut self , batch : Batch ) -> Result < Vec < serde_json:: Value > , Error > {
254
297
let mut id_map = BTreeMap :: new ( ) ;
255
298
let mut raw = Vec :: new ( ) ;
@@ -333,6 +376,8 @@ impl<S: Read + Write> Client<S> {
333
376
Ok ( ( ) )
334
377
}
335
378
379
+ /// Tries to read from the read buffer if any notifications were received since the last call
380
+ /// or `poll`, and processes them
336
381
pub fn poll ( & mut self ) -> Result < ( ) , Error > {
337
382
// try to pull data from the stream
338
383
self . buf_reader . fill_buf ( ) ?;
@@ -350,19 +395,23 @@ impl<S: Read + Write> Client<S> {
350
395
Ok ( ( ) )
351
396
}
352
397
398
+ /// Subscribes to notifications for new block headers, by sending a `blockchain.headers.subscribe` call.
353
399
pub fn block_headers_subscribe ( & mut self ) -> Result < HeaderNotification , Error > {
354
400
let req = Request :: new ( "blockchain.headers.subscribe" , vec ! [ ] ) ;
355
401
let value = self . call ( req) ?;
356
402
357
403
Ok ( serde_json:: from_value ( value) ?)
358
404
}
359
405
406
+ /// Tries to pop one queued notification for a new block header that we might have received.
407
+ /// Returns `None` if there are no items in the queue.
360
408
pub fn block_headers_poll ( & mut self ) -> Result < Option < HeaderNotification > , Error > {
361
409
self . poll ( ) ?;
362
410
363
411
Ok ( self . headers . pop_front ( ) )
364
412
}
365
413
414
+ /// Gets the block header for height `height`.
366
415
pub fn block_header ( & mut self , height : usize ) -> Result < block:: BlockHeader , Error > {
367
416
let req = Request :: new ( "blockchain.block.header" , vec ! [ Param :: Usize ( height) ] ) ;
368
417
let result = self . call ( req) ?;
@@ -374,6 +423,7 @@ impl<S: Read + Write> Client<S> {
374
423
) ?) ?)
375
424
}
376
425
426
+ /// Tries to fetch `count` block headers starting from `start_height`.
377
427
pub fn block_headers (
378
428
& mut self ,
379
429
start_height : usize ,
@@ -397,6 +447,7 @@ impl<S: Read + Write> Client<S> {
397
447
Ok ( deserialized)
398
448
}
399
449
450
+ /// Estimates the fee required in **Satoshis per kilobyte** to confirm a transaction in `number` blocks.
400
451
pub fn estimate_fee ( & mut self , number : usize ) -> Result < f64 , Error > {
401
452
let req = Request :: new ( "blockchain.estimatefee" , vec ! [ Param :: Usize ( number) ] ) ;
402
453
let result = self . call ( req) ?;
@@ -406,6 +457,7 @@ impl<S: Read + Write> Client<S> {
406
457
. ok_or_else ( || Error :: InvalidResponse ( result. clone ( ) ) )
407
458
}
408
459
460
+ /// Returns the minimum accepted fee by the server's node in **Bitcoin, not Satoshi**.
409
461
pub fn relay_fee ( & mut self ) -> Result < f64 , Error > {
410
462
let req = Request :: new ( "blockchain.relayfee" , vec ! [ ] ) ;
411
463
let result = self . call ( req) ?;
@@ -415,6 +467,13 @@ impl<S: Read + Write> Client<S> {
415
467
. ok_or_else ( || Error :: InvalidResponse ( result. clone ( ) ) )
416
468
}
417
469
470
+ /// Subscribes to notifications for activity on a specific *scriptPubKey*.
471
+ ///
472
+ /// Returns a [`ScriptStatus`](../types/type.ScriptStatus.html) when successful that represents
473
+ /// the current status for the requested script.
474
+ ///
475
+ /// Returns [`Error::AlreadySubscribed`](../types/enum.Error.html#variant.AlreadySubscribed) if
476
+ /// already subscribed to the same script.
418
477
pub fn script_subscribe ( & mut self , script : & Script ) -> Result < ScriptStatus , Error > {
419
478
let script_hash = script. to_electrum_scripthash ( ) ;
420
479
@@ -434,6 +493,12 @@ impl<S: Read + Write> Client<S> {
434
493
Ok ( serde_json:: from_value ( value) ?)
435
494
}
436
495
496
+ /// Subscribes to notifications for activity on a specific *scriptPubKey*.
497
+ ///
498
+ /// Returns a `bool` with the server response when successful.
499
+ ///
500
+ /// Returns [`Error::NotSubscribed`](../types/enum.Error.html#variant.NotSubscribed) if
501
+ /// not subscribed to the script.
437
502
pub fn script_unsubscribe ( & mut self , script : & Script ) -> Result < bool , Error > {
438
503
let script_hash = script. to_electrum_scripthash ( ) ;
439
504
@@ -453,6 +518,7 @@ impl<S: Read + Write> Client<S> {
453
518
Ok ( answer)
454
519
}
455
520
521
+ /// Tries to pop one queued notification for a the requested script. Returns `None` if there are no items in the queue.
456
522
pub fn script_poll ( & mut self , script : & Script ) -> Result < Option < ScriptStatus > , Error > {
457
523
self . poll ( ) ?;
458
524
@@ -464,50 +530,63 @@ impl<S: Read + Write> Client<S> {
464
530
}
465
531
}
466
532
533
+ /// Returns the balance for a *scriptPubKey*
467
534
pub fn script_get_balance ( & mut self , script : & Script ) -> Result < GetBalanceRes , Error > {
468
535
let params = vec ! [ Param :: String ( script. to_electrum_scripthash( ) . to_hex( ) ) ] ;
469
536
let req = Request :: new ( "blockchain.scripthash.get_balance" , params) ;
470
537
let result = self . call ( req) ?;
471
538
472
539
Ok ( serde_json:: from_value ( result) ?)
473
540
}
541
+ /// Batch version of [`script_get_balance`](#method.script_get_balance).
542
+ ///
543
+ /// Takes a list of scripts and returns a list of balance responses.
474
544
pub fn batch_script_get_balance (
475
545
& mut self ,
476
546
scripts : Vec < & Script > ,
477
547
) -> Result < Vec < GetBalanceRes > , Error > {
478
548
impl_batch_call ! ( self , scripts, script_get_balance)
479
549
}
480
550
551
+ /// Returns the history for a *scriptPubKey*
481
552
pub fn script_get_history ( & mut self , script : & Script ) -> Result < Vec < GetHistoryRes > , Error > {
482
553
let params = vec ! [ Param :: String ( script. to_electrum_scripthash( ) . to_hex( ) ) ] ;
483
554
let req = Request :: new ( "blockchain.scripthash.get_history" , params) ;
484
555
let result = self . call ( req) ?;
485
556
486
557
Ok ( serde_json:: from_value ( result) ?)
487
558
}
559
+ /// Batch version of [`script_get_history`](#method.script_get_history).
560
+ ///
561
+ /// Takes a list of scripts and returns a list of history responses.
488
562
pub fn batch_script_get_history (
489
563
& mut self ,
490
564
scripts : Vec < & Script > ,
491
565
) -> Result < Vec < Vec < GetHistoryRes > > , Error > {
492
566
impl_batch_call ! ( self , scripts, script_get_history)
493
567
}
494
568
569
+ /// Returns the list of unspent outputs for a *scriptPubKey*
495
570
pub fn script_list_unspent ( & mut self , script : & Script ) -> Result < Vec < ListUnspentRes > , Error > {
496
571
let params = vec ! [ Param :: String ( script. to_electrum_scripthash( ) . to_hex( ) ) ] ;
497
572
let req = Request :: new ( "blockchain.scripthash.listunspent" , params) ;
498
573
let result = self . call ( req) ?;
499
574
500
575
Ok ( serde_json:: from_value ( result) ?)
501
576
}
577
+ /// Batch version of [`script_list_unspent`](#method.script_list_unspent).
578
+ ///
579
+ /// Takes a list of scripts and returns a list of a list of utxos.
502
580
pub fn batch_script_list_unspent (
503
581
& mut self ,
504
582
scripts : Vec < & Script > ,
505
583
) -> Result < Vec < Vec < ListUnspentRes > > , Error > {
506
584
impl_batch_call ! ( self , scripts, script_list_unspent)
507
585
}
508
586
509
- pub fn transaction_get ( & mut self , tx_hash : & Txid ) -> Result < Transaction , Error > {
510
- let params = vec ! [ Param :: String ( tx_hash. to_hex( ) ) ] ;
587
+ /// Gets the raw transaction with `txid`. Returns an error if not found.
588
+ pub fn transaction_get ( & mut self , txid : & Txid ) -> Result < Transaction , Error > {
589
+ let params = vec ! [ Param :: String ( txid. to_hex( ) ) ] ;
511
590
let req = Request :: new ( "blockchain.transaction.get" , params) ;
512
591
let result = self . call ( req) ?;
513
592
@@ -517,13 +596,14 @@ impl<S: Read + Write> Client<S> {
517
596
. ok_or_else ( || Error :: InvalidResponse ( result. clone ( ) ) ) ?,
518
597
) ?) ?)
519
598
}
520
- pub fn batch_transaction_get (
521
- & mut self ,
522
- tx_hashes : Vec < & Txid > ,
523
- ) -> Result < Vec < Transaction > , Error > {
524
- impl_batch_call ! ( self , tx_hashes , transaction_get)
599
+ /// Batch version of [`transaction_get`](#method.transaction_get).
600
+ ///
601
+ /// Takes a list of `txids` and returns a list of transactions.
602
+ pub fn batch_transaction_get ( & mut self , txids : Vec < & Txid > ) -> Result < Vec < Transaction > , Error > {
603
+ impl_batch_call ! ( self , txids , transaction_get)
525
604
}
526
605
606
+ /// Broadcasts a transaction to the network.
527
607
pub fn transaction_broadcast ( & mut self , tx : & Transaction ) -> Result < Txid , Error > {
528
608
let buffer: Vec < u8 > = serialize ( tx) ;
529
609
let params = vec ! [ Param :: String ( buffer. to_hex( ) ) ] ;
@@ -533,6 +613,7 @@ impl<S: Read + Write> Client<S> {
533
613
Ok ( serde_json:: from_value ( result) ?)
534
614
}
535
615
616
+ /// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
536
617
pub fn transaction_get_merkle (
537
618
& mut self ,
538
619
txid : & Txid ,
@@ -545,6 +626,7 @@ impl<S: Read + Write> Client<S> {
545
626
Ok ( serde_json:: from_value ( result) ?)
546
627
}
547
628
629
+ /// Returns the capabilities of the server.
548
630
pub fn server_features ( & mut self ) -> Result < ServerFeaturesRes , Error > {
549
631
let req = Request :: new ( "server.features" , vec ! [ ] ) ;
550
632
let result = self . call ( req) ?;
@@ -553,19 +635,20 @@ impl<S: Read + Write> Client<S> {
553
635
}
554
636
555
637
#[ cfg( feature = "debug-calls" ) ]
638
+ /// Returns the number of network calls made since the creation of the client.
556
639
pub fn calls_made ( & self ) -> usize {
557
640
self . calls
558
641
}
559
642
560
643
#[ inline]
561
644
#[ cfg( feature = "debug-calls" ) ]
562
- pub fn increment_calls ( & mut self ) {
645
+ fn increment_calls ( & mut self ) {
563
646
self . calls += 1 ;
564
647
}
565
648
566
649
#[ inline]
567
650
#[ cfg( not( feature = "debug-calls" ) ) ]
568
- pub fn increment_calls ( & self ) { }
651
+ fn increment_calls ( & self ) { }
569
652
}
570
653
571
654
#[ cfg( test) ]
0 commit comments