New interface
This commit is contained in:
parent
db4554f9e2
commit
f9b318c1c6
@ -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" }
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
187
src/api.rs
Normal 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;
|
||||
}
|
||||
1123
src/client.rs
1123
src/client.rs
File diff suppressed because it is too large
Load Diff
13
src/lib.rs
13
src/lib.rs
@ -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
1079
src/raw_client.rs
Normal file
File diff suppressed because it is too large
Load Diff
27
src/types.rs
27
src/types.rs
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user