New interface

This commit is contained in:
Alekos Filini 2020-07-15 15:54:34 +02:00
parent db4554f9e2
commit f9b318c1c6
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
9 changed files with 1437 additions and 1013 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "electrum-client"
version = "0.1.0-beta.6"
version = "0.2.0-beta.1"
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
license = "MIT"
homepage = "https://github.com/MagicalBitcoin/rust-electrum-client"
@ -18,7 +18,6 @@ path = "src/lib.rs"
[dependencies]
log = "^0.4"
env_logger = "0.7"
bitcoin = { version = "0.23", features = ["use-serde"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }

View File

@ -1,9 +1,9 @@
extern crate electrum_client;
use electrum_client::Client;
use electrum_client::{Client, ElectrumApi};
fn main() {
let client = Client::new("kirsche.emzy.de:50001").unwrap();
let client = Client::new("tcp://electrum.blockstream.info:50001", None).unwrap();
let res = client.server_features();
println!("{:#?}", res);
}

View File

@ -1,9 +1,9 @@
extern crate electrum_client;
use electrum_client::Client;
use electrum_client::{Client, ElectrumApi};
fn main() {
let client = Client::new_ssl("electrum2.hodlister.co:50002", true).unwrap();
let client = Client::new("ssl://electrum.blockstream.info:50002", None).unwrap();
let res = client.server_features();
println!("{:#?}", res);
}

View File

@ -1,19 +1,19 @@
extern crate electrum_client;
use electrum_client::Client;
use electrum_client::{Client, ElectrumApi};
fn main() {
// NOTE: This assumes Tor is running localy, with an unauthenticated Socks5 listening at
// localhost:9050
let client = Client::new_proxy("ozahtqwp25chjdjd.onion:50001", "127.0.0.1:9050").unwrap();
let client = Client::new("tcp://explorernuoc63nb.onion:110", Some("127.0.0.1:9050")).unwrap();
let res = client.server_features();
println!("{:#?}", res);
// works both with onion v2/v3 (if your Tor supports them)
let client = Client::new_proxy(
"v7gtzf7nua6hdmb2wtqaqioqmesdb4xrlly4zwr7bvayxv2bpg665pqd.onion:50001",
"127.0.0.1:9050",
let client = Client::new(
"tcp://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion:110",
Some("127.0.0.1:9050"),
)
.unwrap();
let res = client.server_features();

187
src/api.rs Normal file
View File

@ -0,0 +1,187 @@
//! Electrum APIs
use std::convert::TryInto;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::{BlockHeader, Script, Transaction, Txid};
use batch::Batch;
use types::*;
/// API calls exposed by an Electrum client
pub trait ElectrumApi {
/// Gets the block header for height `height`.
fn block_header(&self, height: usize) -> Result<BlockHeader, Error> {
Ok(deserialize(&self.block_header_raw(height)?)?)
}
/// Subscribes to notifications for new block headers, by sending a `blockchain.headers.subscribe` call.
fn block_headers_subscribe(&self) -> Result<HeaderNotification, Error> {
self.block_headers_subscribe_raw()?.try_into()
}
/// Tries to pop one queued notification for a new block header that we might have received.
/// Returns `None` if there are no items in the queue.
fn block_headers_pop(&self) -> Result<Option<HeaderNotification>, Error> {
self.block_headers_pop_raw()?
.map(|raw| raw.try_into())
.transpose()
}
/// Gets the transaction with `txid`. Returns an error if not found.
fn transaction_get(&self, txid: &Txid) -> Result<Transaction, Error> {
Ok(deserialize(&self.transaction_get_raw(txid)?)?)
}
/// Batch version of [`transaction_get`](#method.transaction_get).
///
/// Takes a list of `txids` and returns a list of transactions.
fn batch_transaction_get<'t, I>(&self, txids: I) -> Result<Vec<Transaction>, Error>
where
I: IntoIterator<Item = &'t Txid>,
{
self.batch_transaction_get_raw(txids)?
.iter()
.map(|s| Ok(deserialize(s)?))
.collect()
}
/// Batch version of [`block_header`](#method.block_header).
///
/// Takes a list of `heights` of blocks and returns a list of headers.
fn batch_block_header<'s, I>(&self, heights: I) -> Result<Vec<BlockHeader>, Error>
where
I: IntoIterator<Item = u32>,
{
self.batch_block_header_raw(heights)?
.iter()
.map(|s| Ok(deserialize(s)?))
.collect()
}
/// Broadcasts a transaction to the network.
fn transaction_broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
let buffer: Vec<u8> = serialize(tx);
self.transaction_broadcast_raw(&buffer)
}
/// Execute a queue of calls stored in a [`Batch`](../batch/struct.Batch.html) struct. Returns
/// `Ok()` **only if** all of the calls are successful. The order of the JSON `Value`s returned
/// reflects the order in which the calls were made on the `Batch` struct.
fn batch_call(&self, batch: Batch) -> Result<Vec<serde_json::Value>, Error>;
/// Subscribes to notifications for new block headers, by sending a `blockchain.headers.subscribe` call and
/// returns the current tip as raw bytes instead of deserializing them.
fn block_headers_subscribe_raw(&self) -> Result<RawHeaderNotification, Error>;
/// Tries to pop one queued notification for a new block header that we might have received.
/// Returns a the header in raw bytes if a notification is found in the queue, None otherwise.
fn block_headers_pop_raw(&self) -> Result<Option<RawHeaderNotification>, Error>;
/// Gets the raw bytes of block header for height `height`.
fn block_header_raw(&self, height: usize) -> Result<Vec<u8>, Error>;
/// Tries to fetch `count` block headers starting from `start_height`.
fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error>;
/// Estimates the fee required in **Satoshis per kilobyte** to confirm a transaction in `number` blocks.
fn estimate_fee(&self, number: usize) -> Result<f64, Error>;
/// Returns the minimum accepted fee by the server's node in **Bitcoin, not Satoshi**.
fn relay_fee(&self) -> Result<f64, Error>;
/// Subscribes to notifications for activity on a specific *scriptPubKey*.
///
/// Returns a [`ScriptStatus`](../types/type.ScriptStatus.html) when successful that represents
/// the current status for the requested script.
///
/// Returns [`Error::AlreadySubscribed`](../types/enum.Error.html#variant.AlreadySubscribed) if
/// already subscribed to the script.
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error>;
/// Subscribes to notifications for activity on a specific *scriptPubKey*.
///
/// Returns a `bool` with the server response when successful.
///
/// Returns [`Error::NotSubscribed`](../types/enum.Error.html#variant.NotSubscribed) if
/// not subscribed to the script.
fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error>;
/// Tries to pop one queued notification for a the requested script. Returns `None` if there are no items in the queue.
fn script_pop(&self, script: &Script) -> Result<Option<ScriptStatus>, Error>;
/// Returns the balance for a *scriptPubKey*.
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes, Error>;
/// Batch version of [`script_get_balance`](#method.script_get_balance).
///
/// Takes a list of scripts and returns a list of balance responses.
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
where
I: IntoIterator<Item = &'s Script>;
/// Returns the history for a *scriptPubKey*
fn script_get_history(&self, script: &Script) -> Result<Vec<GetHistoryRes>, Error>;
/// Batch version of [`script_get_history`](#method.script_get_history).
///
/// Takes a list of scripts and returns a list of history responses.
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
where
I: IntoIterator<Item = &'s Script>;
/// Returns the list of unspent outputs for a *scriptPubKey*
fn script_list_unspent(&self, script: &Script) -> Result<Vec<ListUnspentRes>, Error>;
/// Batch version of [`script_list_unspent`](#method.script_list_unspent).
///
/// Takes a list of scripts and returns a list of a list of utxos.
fn batch_script_list_unspent<'s, I>(
&self,
scripts: I,
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
where
I: IntoIterator<Item = &'s Script>;
/// Gets the raw bytes of a transaction with `txid`. Returns an error if not found.
fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error>;
/// Batch version of [`transaction_get_raw`](#method.transaction_get_raw).
///
/// Takes a list of `txids` and returns a list of transactions raw bytes.
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
where
I: IntoIterator<Item = &'t Txid>;
/// Batch version of [`block_header_raw`](#method.block_header_raw).
///
/// Takes a list of `heights` of blocks and returns a list of block header raw bytes.
fn batch_block_header_raw<'s, I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
where
I: IntoIterator<Item = u32>;
/// Batch version of [`estimate_fee`](#method.estimate_fee).
///
/// Takes a list of `numbers` of blocks and returns a list of fee required in
/// **Satoshis per kilobyte** to confirm a transaction in the given number of blocks.
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
where
I: IntoIterator<Item = usize>;
/// Broadcasts the raw bytes of a transaction to the network.
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error>;
/// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error>;
/// Returns the capabilities of the server.
fn server_features(&self) -> Result<ServerFeaturesRes, Error>;
/// Pings the server. This method can also be used as a "dummy" call to trigger the processing
/// of incoming block header or script notifications.
fn ping(&self) -> Result<(), Error>;
#[cfg(feature = "debug-calls")]
/// Returns the number of network calls made since the creation of the client.
fn calls_made(&self) -> usize;
}

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,9 @@
//! # Example
//!
//! ```no_run
//! use electrum_client::Client;
//! use electrum_client::{Client, ElectrumApi};
//!
//! let mut client = Client::new("kirsche.emzy.de:50001")?;
//! let mut client = Client::new("tcp://electrum.blockstream.info:50001", None)?;
//! let response = client.server_features()?;
//! # Ok::<(), electrum_client::Error>(())
//! ```
@ -38,11 +38,14 @@ extern crate webpki;
#[cfg(any(feature = "use-rustls", feature = "default"))]
extern crate webpki_roots;
pub mod batch;
mod api;
mod batch;
pub mod client;
pub mod raw_client;
mod stream;
pub mod types;
mod types;
pub use api::ElectrumApi;
pub use batch::Batch;
pub use client::Client;
pub use client::*;
pub use types::*;

1079
src/raw_client.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,11 @@
//!
//! This module contains definitions of all the complex data structures that are returned by calls
use std::convert::TryFrom;
use std::ops::Deref;
use bitcoin::blockdata::block;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::{Script, Txid};
@ -136,8 +138,6 @@ fn from_hex_header<'de, D>(deserializer: D) -> Result<block::BlockHeader, D::Err
where
D: de::Deserializer<'de>,
{
use bitcoin::consensus::deserialize;
let vec: Vec<u8> = from_hex(deserializer)?;
deserialize(&vec).map_err(de::Error::custom)
}
@ -231,6 +231,27 @@ pub struct HeaderNotification {
pub header: block::BlockHeader,
}
/// Notification of a new block header with the header encoded as raw bytes
#[derive(Debug, Deserialize)]
pub struct RawHeaderNotification {
/// New block height.
pub height: usize,
/// Newly added header.
#[serde(rename = "hex", deserialize_with = "from_hex")]
pub header: Vec<u8>,
}
impl TryFrom<RawHeaderNotification> for HeaderNotification {
type Error = Error;
fn try_from(raw: RawHeaderNotification) -> Result<Self, Self::Error> {
Ok(HeaderNotification {
height: raw.height,
header: deserialize(&raw.header)?,
})
}
}
/// Notification of the new status of a script
#[derive(Debug, Deserialize)]
pub struct ScriptNotification {
@ -265,6 +286,8 @@ pub enum Error {
InvalidDNSNameError(String),
/// Missing domain while it was explicitly asked to validate it
MissingDomain,
/// SSL over a socks5 proxy is currently not supported
SSLOverSocks5,
/// Couldn't take a lock on the reader mutex. This means that there's already another reader
/// thread running