Skip to content

Commit a7ec4ce

Browse files
committed
Add docs
1 parent 496fa2c commit a7ec4ce

File tree

4 files changed

+204
-22
lines changed

4 files changed

+204
-22
lines changed

src/batch.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,49 @@
1+
//! Batch utilities
2+
//!
3+
//! This module contains definitions and helper functions used when making batch calls.
4+
15
use bitcoin::hashes::hex::ToHex;
26
use bitcoin::{Script, Txid};
37

48
use types::{Param, ToElectrumScriptHash};
59

10+
/// Helper structure that caches all the requests before they are actually sent to the server.
11+
///
12+
/// Calls on this function are stored and run when [`batch_call`](../client/struct.Client.html#method.batch_call)
13+
/// is run on a [`Client`](../client/struct.Client.html).
14+
///
15+
/// This structure can be used to make multiple *different* calls in one single run. For batch
16+
/// calls of the same type, there are shorthands methods defined on the
17+
/// [`Client`](../client/struct.Client.html), like
18+
/// [`batch_script_get_balance`](../client/struct.Client.html#method.batch_script_get_balance) to ask the
19+
/// server for the balance of multiple scripts with a single request.
620
pub struct Batch {
721
calls: Vec<(String, Vec<Param>)>,
822
}
923

1024
impl Batch {
25+
/// Add one `blockchain.scripthash.listunspent` request to the batch queue
1126
pub fn script_list_unspent(&mut self, script: &Script) {
1227
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
1328
self.calls
1429
.push((String::from("blockchain.scripthash.listunspent"), params));
1530
}
1631

32+
/// Add one `blockchain.scripthash.get_history` request to the batch queue
1733
pub fn script_get_history(&mut self, script: &Script) {
1834
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
1935
self.calls
2036
.push((String::from("blockchain.scripthash.get_history"), params));
2137
}
2238

39+
/// Add one `blockchain.scripthash.get_balance` request to the batch queue
2340
pub fn script_get_balance(&mut self, script: &Script) {
2441
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
2542
self.calls
2643
.push((String::from("blockchain.scripthash.get_balance"), params));
2744
}
2845

46+
/// Add one `blockchain.transaction.get` request to the batch queue
2947
pub fn transaction_get(&mut self, tx_hash: &Txid) {
3048
let params = vec![Param::String(tx_hash.to_hex())];
3149
self.calls

src/client.rs

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! Electrum client
2+
//!
3+
//! This module contains definitions of all the complex data structures that are returned by calls
4+
15
use std::collections::{BTreeMap, VecDeque};
26
#[cfg(test)]
37
use std::fs::File;
@@ -55,6 +59,20 @@ macro_rules! impl_batch_call {
5559
}};
5660
}
5761

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.
5876
#[derive(Debug)]
5977
pub struct Client<S>
6078
where
@@ -70,8 +88,11 @@ where
7088
calls: usize,
7189
}
7290

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> {
7596
let stream = TcpStream::connect(socket_addr)?;
7697
let buf_reader = BufReader::new(stream.try_clone()?);
7798

@@ -88,7 +109,12 @@ impl Client<TcpStream> {
88109
}
89110

90111
#[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.
92118
pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
93119
let mut builder =
94120
SslConnector::builder(SslMethod::tls()).map_err(Error::InvalidSslMethod)?;
@@ -145,7 +171,15 @@ mod danger {
145171
any(feature = "default", feature = "use-rustls"),
146172
not(feature = "use-openssl")
147173
))]
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.
149183
pub fn new_ssl<A: ToSocketAddrs>(socket_addr: A, domain: Option<&str>) -> Result<Self, Error> {
150184
let mut config = ClientConfig::new();
151185
if domain.is_none() {
@@ -183,7 +217,13 @@ impl Client<ClonableStream<StreamOwned<ClientSession, TcpStream>>> {
183217
}
184218

185219
#[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.
187227
pub fn new_proxy<A: ToSocketAddrs, T: ToTargetAddr>(
188228
target_addr: T,
189229
proxy_addr: A,
@@ -250,6 +290,9 @@ impl<S: Read + Write> Client<S> {
250290
Ok(resp["result"].take())
251291
}
252292

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.
253296
pub fn batch_call(&mut self, batch: Batch) -> Result<Vec<serde_json::Value>, Error> {
254297
let mut id_map = BTreeMap::new();
255298
let mut raw = Vec::new();
@@ -333,6 +376,8 @@ impl<S: Read + Write> Client<S> {
333376
Ok(())
334377
}
335378

379+
/// Tries to read from the read buffer if any notifications were received since the last call
380+
/// or `poll`, and processes them
336381
pub fn poll(&mut self) -> Result<(), Error> {
337382
// try to pull data from the stream
338383
self.buf_reader.fill_buf()?;
@@ -350,19 +395,23 @@ impl<S: Read + Write> Client<S> {
350395
Ok(())
351396
}
352397

398+
/// Subscribes to notifications for new block headers, by sending a `blockchain.headers.subscribe` call.
353399
pub fn block_headers_subscribe(&mut self) -> Result<HeaderNotification, Error> {
354400
let req = Request::new("blockchain.headers.subscribe", vec![]);
355401
let value = self.call(req)?;
356402

357403
Ok(serde_json::from_value(value)?)
358404
}
359405

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.
360408
pub fn block_headers_poll(&mut self) -> Result<Option<HeaderNotification>, Error> {
361409
self.poll()?;
362410

363411
Ok(self.headers.pop_front())
364412
}
365413

414+
/// Gets the block header for height `height`.
366415
pub fn block_header(&mut self, height: usize) -> Result<block::BlockHeader, Error> {
367416
let req = Request::new("blockchain.block.header", vec![Param::Usize(height)]);
368417
let result = self.call(req)?;
@@ -374,6 +423,7 @@ impl<S: Read + Write> Client<S> {
374423
)?)?)
375424
}
376425

426+
/// Tries to fetch `count` block headers starting from `start_height`.
377427
pub fn block_headers(
378428
&mut self,
379429
start_height: usize,
@@ -397,6 +447,7 @@ impl<S: Read + Write> Client<S> {
397447
Ok(deserialized)
398448
}
399449

450+
/// Estimates the fee required in **Satoshis per kilobyte** to confirm a transaction in `number` blocks.
400451
pub fn estimate_fee(&mut self, number: usize) -> Result<f64, Error> {
401452
let req = Request::new("blockchain.estimatefee", vec![Param::Usize(number)]);
402453
let result = self.call(req)?;
@@ -406,6 +457,7 @@ impl<S: Read + Write> Client<S> {
406457
.ok_or_else(|| Error::InvalidResponse(result.clone()))
407458
}
408459

460+
/// Returns the minimum accepted fee by the server's node in **Bitcoin, not Satoshi**.
409461
pub fn relay_fee(&mut self) -> Result<f64, Error> {
410462
let req = Request::new("blockchain.relayfee", vec![]);
411463
let result = self.call(req)?;
@@ -415,6 +467,13 @@ impl<S: Read + Write> Client<S> {
415467
.ok_or_else(|| Error::InvalidResponse(result.clone()))
416468
}
417469

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.
418477
pub fn script_subscribe(&mut self, script: &Script) -> Result<ScriptStatus, Error> {
419478
let script_hash = script.to_electrum_scripthash();
420479

@@ -434,6 +493,12 @@ impl<S: Read + Write> Client<S> {
434493
Ok(serde_json::from_value(value)?)
435494
}
436495

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.
437502
pub fn script_unsubscribe(&mut self, script: &Script) -> Result<bool, Error> {
438503
let script_hash = script.to_electrum_scripthash();
439504

@@ -453,6 +518,7 @@ impl<S: Read + Write> Client<S> {
453518
Ok(answer)
454519
}
455520

521+
/// Tries to pop one queued notification for a the requested script. Returns `None` if there are no items in the queue.
456522
pub fn script_poll(&mut self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
457523
self.poll()?;
458524

@@ -464,50 +530,63 @@ impl<S: Read + Write> Client<S> {
464530
}
465531
}
466532

533+
/// Returns the balance for a *scriptPubKey*
467534
pub fn script_get_balance(&mut self, script: &Script) -> Result<GetBalanceRes, Error> {
468535
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
469536
let req = Request::new("blockchain.scripthash.get_balance", params);
470537
let result = self.call(req)?;
471538

472539
Ok(serde_json::from_value(result)?)
473540
}
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.
474544
pub fn batch_script_get_balance(
475545
&mut self,
476546
scripts: Vec<&Script>,
477547
) -> Result<Vec<GetBalanceRes>, Error> {
478548
impl_batch_call!(self, scripts, script_get_balance)
479549
}
480550

551+
/// Returns the history for a *scriptPubKey*
481552
pub fn script_get_history(&mut self, script: &Script) -> Result<Vec<GetHistoryRes>, Error> {
482553
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
483554
let req = Request::new("blockchain.scripthash.get_history", params);
484555
let result = self.call(req)?;
485556

486557
Ok(serde_json::from_value(result)?)
487558
}
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.
488562
pub fn batch_script_get_history(
489563
&mut self,
490564
scripts: Vec<&Script>,
491565
) -> Result<Vec<Vec<GetHistoryRes>>, Error> {
492566
impl_batch_call!(self, scripts, script_get_history)
493567
}
494568

569+
/// Returns the list of unspent outputs for a *scriptPubKey*
495570
pub fn script_list_unspent(&mut self, script: &Script) -> Result<Vec<ListUnspentRes>, Error> {
496571
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
497572
let req = Request::new("blockchain.scripthash.listunspent", params);
498573
let result = self.call(req)?;
499574

500575
Ok(serde_json::from_value(result)?)
501576
}
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.
502580
pub fn batch_script_list_unspent(
503581
&mut self,
504582
scripts: Vec<&Script>,
505583
) -> Result<Vec<Vec<ListUnspentRes>>, Error> {
506584
impl_batch_call!(self, scripts, script_list_unspent)
507585
}
508586

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())];
511590
let req = Request::new("blockchain.transaction.get", params);
512591
let result = self.call(req)?;
513592

@@ -517,13 +596,14 @@ impl<S: Read + Write> Client<S> {
517596
.ok_or_else(|| Error::InvalidResponse(result.clone()))?,
518597
)?)?)
519598
}
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)
525604
}
526605

606+
/// Broadcasts a transaction to the network.
527607
pub fn transaction_broadcast(&mut self, tx: &Transaction) -> Result<Txid, Error> {
528608
let buffer: Vec<u8> = serialize(tx);
529609
let params = vec![Param::String(buffer.to_hex())];
@@ -533,6 +613,7 @@ impl<S: Read + Write> Client<S> {
533613
Ok(serde_json::from_value(result)?)
534614
}
535615

616+
/// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
536617
pub fn transaction_get_merkle(
537618
&mut self,
538619
txid: &Txid,
@@ -545,6 +626,7 @@ impl<S: Read + Write> Client<S> {
545626
Ok(serde_json::from_value(result)?)
546627
}
547628

629+
/// Returns the capabilities of the server.
548630
pub fn server_features(&mut self) -> Result<ServerFeaturesRes, Error> {
549631
let req = Request::new("server.features", vec![]);
550632
let result = self.call(req)?;
@@ -553,19 +635,20 @@ impl<S: Read + Write> Client<S> {
553635
}
554636

555637
#[cfg(feature = "debug-calls")]
638+
/// Returns the number of network calls made since the creation of the client.
556639
pub fn calls_made(&self) -> usize {
557640
self.calls
558641
}
559642

560643
#[inline]
561644
#[cfg(feature = "debug-calls")]
562-
pub fn increment_calls(&mut self) {
645+
fn increment_calls(&mut self) {
563646
self.calls += 1;
564647
}
565648

566649
#[inline]
567650
#[cfg(not(feature = "debug-calls"))]
568-
pub fn increment_calls(&self) {}
651+
fn increment_calls(&self) {}
569652
}
570653

571654
#[cfg(test)]

src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
#![warn(missing_docs)]
2+
3+
//! This library provides an extendable Bitcoin-Electrum client that supports batch calls,
4+
//! notifications and multiple transport methods.
5+
//!
6+
//! By default this library is compiled with support for SSL servers using [`rustls`](https;//docs.rs/rustls) and support for
7+
//! plaintext connections over a socks proxy, useful for Onion servers. Using different features,
8+
//! the SSL implementation can be removed or replaced with [`openssl`](https://docs.rs/openssl).
9+
//!
10+
//! A `minimal` configuration is also provided, which only includes the plaintext TCP client.
11+
//!
12+
//! # Example
13+
//!
14+
//! ```no_run
15+
//! use electrum_client::Client;
16+
//!
17+
//! let mut client = Client::new("kirsche.emzy.de:50001")?;
18+
//! let response = client.server_features()?;
19+
//! # Ok::<(), electrum_client::Error>(())
20+
//! ```
21+
122
pub extern crate bitcoin;
223
extern crate log;
324
#[cfg(feature = "use-openssl")]

0 commit comments

Comments
 (0)