Skip to content
6 changes: 1 addition & 5 deletions .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
name: Test
runs-on: ubuntu-latest
env:
TEST_ELECTRUM_SERVER: electrum.blockstream.info:50001
TEST_ELECTRUM_SERVER: fortress.qtornado.com:443
strategy:
matrix:
rust:
Expand All @@ -30,10 +30,6 @@ jobs:
toolchain: ${{ matrix.rust }}
- name: Test
run: cargo test --verbose --all-features
- name: Setup iptables for the timeout test
run: sudo ip6tables -I INPUT 1 -p tcp -d ::1 --dport 60000 -j DROP
- name: Timeout test
run: cargo test -- --ignored test_local_timeout
- run: cargo check --verbose --features=use-openssl
- run: cargo check --verbose --no-default-features --features=proxy
- run: cargo check --verbose --no-default-features --features=minimal
Expand Down
73 changes: 69 additions & 4 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ where
(**self).block_headers(start_height, count)
}

fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
(**self).estimate_fee(number)
fn estimate_fee(&self, number: usize, mode: Option<EstimationMode>) -> Result<f64, Error> {
(**self).estimate_fee(number, mode)
}

fn relay_fee(&self) -> Result<f64, Error> {
Expand Down Expand Up @@ -141,6 +141,13 @@ where
(**self).transaction_broadcast_raw(raw_tx)
}

fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
&self,
raw_txs: &[T],
) -> Result<BroadcastPackageRes, Error> {
(**self).transaction_broadcast_package_raw(raw_txs)
}

fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
(**self).transaction_get_merkle(txid, height)
}
Expand Down Expand Up @@ -172,6 +179,10 @@ where
(**self).server_features()
}

fn mempool_get_info(&self) -> Result<MempoolInfoRes, Error> {
(**self).mempool_get_info()
}

fn ping(&self) -> Result<(), Error> {
(**self).ping()
}
Expand Down Expand Up @@ -241,6 +252,21 @@ pub trait ElectrumApi {
self.transaction_broadcast_raw(&buffer)
}

/// Broadcasts a package of transactions to the network.
///
/// The package must consist of a child with its parents, where none of the parents
/// depend on one another. The package must be topologically sorted, with the child
/// being the last element in the array.
///
/// This method was added in protocol v1.6 for package relay support.
fn transaction_broadcast_package(
&self,
txs: &[Transaction],
) -> Result<BroadcastPackageRes, Error> {
let raw_txs: Vec<Vec<u8>> = txs.iter().map(serialize).collect();
self.transaction_broadcast_package_raw(&raw_txs)
}

/// Executes the requested API call returning the raw answer.
fn raw_call(
&self,
Expand Down Expand Up @@ -268,9 +294,15 @@ pub trait ElectrumApi {
fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error>;

/// Estimates the fee required in **Bitcoin per kilobyte** to confirm a transaction in `number` blocks.
fn estimate_fee(&self, number: usize) -> Result<f64, Error>;
///
/// Optionally takes an [`EstimationMode`] parameter to specify the fee estimation mode.
/// This parameter was added in protocol v1.6.
fn estimate_fee(&self, number: usize, mode: Option<EstimationMode>) -> Result<f64, Error>;

/// Returns the minimum accepted fee by the server's node in **Bitcoin, not Satoshi**.
///
/// **Note:** This method is deprecated in protocol v1.6+. Use [`mempool_get_info`](#method.mempool_get_info)
/// instead, which provides `minrelaytxfee` along with additional mempool fee information.
fn relay_fee(&self) -> Result<f64, Error>;

/// Subscribes to notifications for activity on a specific *scriptPubKey*.
Expand Down Expand Up @@ -370,6 +402,18 @@ pub trait ElectrumApi {
/// Broadcasts the raw bytes of a transaction to the network.
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error>;

/// Broadcasts a package of raw transactions to the network.
///
/// The package must consist of a child with its parents, where none of the parents
/// depend on one another. The package must be topologically sorted, with the child
/// being the last element in the array.
///
/// This method was added in protocol v1.6 for package relay support.
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
&self,
raw_txs: &[T],
) -> Result<BroadcastPackageRes, 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>;

Expand Down Expand Up @@ -398,6 +442,12 @@ pub trait ElectrumApi {
/// Returns the capabilities of the server.
fn server_features(&self) -> Result<ServerFeaturesRes, Error>;

/// Returns information about the current state of the mempool.
///
/// This method was added in protocol v1.6 and replaces `relay_fee` by providing
/// `minrelaytxfee` along with additional mempool fee information.
fn mempool_get_info(&self) -> Result<MempoolInfoRes, 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>;
Expand Down Expand Up @@ -449,7 +499,11 @@ mod test {
unreachable!()
}

fn estimate_fee(&self, _: usize) -> Result<f64, super::Error> {
fn estimate_fee(
&self,
_: usize,
_: Option<super::EstimationMode>,
) -> Result<f64, super::Error> {
unreachable!()
}

Expand Down Expand Up @@ -572,6 +626,13 @@ mod test {
unreachable!()
}

fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
&self,
_: &[T],
) -> Result<super::BroadcastPackageRes, super::Error> {
unreachable!()
}

fn transaction_get_merkle(
&self,
_: &bitcoin::Txid,
Expand Down Expand Up @@ -607,6 +668,10 @@ mod test {
unreachable!()
}

fn mempool_get_info(&self) -> Result<super::MempoolInfoRes, super::Error> {
unreachable!()
}

fn ping(&self) -> Result<(), super::Error> {
unreachable!()
}
Expand Down
9 changes: 6 additions & 3 deletions src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use bitcoin::{Script, Txid};

use crate::types::{Call, Param, ToElectrumScriptHash};
use crate::types::{Call, EstimationMode, Param, ToElectrumScriptHash};

/// Helper structure that caches all the requests before they are actually sent to the server.
///
Expand Down Expand Up @@ -74,8 +74,11 @@ impl Batch {
}

/// Add one `blockchain.estimatefee` request to the batch queue
pub fn estimate_fee(&mut self, number: usize) {
let params = vec![Param::Usize(number)];
pub fn estimate_fee(&mut self, number: usize, mode: Option<EstimationMode>) {
let mut params = vec![Param::Usize(number)];
if let Some(mode) = mode {
params.push(Param::String(mode.to_string()));
}
self.calls
.push((String::from("blockchain.estimatefee"), params));
}
Expand Down
67 changes: 15 additions & 52 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ impl ElectrumApi for Client {
}

#[inline]
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
impl_inner_call!(self, estimate_fee, number)
fn estimate_fee(&self, number: usize, mode: Option<EstimationMode>) -> Result<f64, Error> {
impl_inner_call!(self, estimate_fee, number, mode)
}

#[inline]
Expand Down Expand Up @@ -322,6 +322,14 @@ impl ElectrumApi for Client {
impl_inner_call!(self, transaction_broadcast_raw, raw_tx)
}

#[inline]
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
&self,
raw_txs: &[T],
) -> Result<BroadcastPackageRes, Error> {
impl_inner_call!(self, transaction_broadcast_package_raw, raw_txs)
}

#[inline]
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
impl_inner_call!(self, transaction_get_merkle, txid, height)
Expand Down Expand Up @@ -362,6 +370,11 @@ impl ElectrumApi for Client {
impl_inner_call!(self, server_features)
}

#[inline]
fn mempool_get_info(&self) -> Result<MempoolInfoRes, Error> {
impl_inner_call!(self, mempool_get_info)
}

#[inline]
fn ping(&self) -> Result<(), Error> {
impl_inner_call!(self, ping)
Expand Down Expand Up @@ -407,54 +420,4 @@ mod tests {

assert!(!exhausted)
}

#[test]
#[ignore]
fn test_local_timeout() {
// This test assumes a couple things:
// - that `localhost` is resolved to two IP addresses, `127.0.0.1` and `::1` (with the v6
// one having higher priority)
// - that the system silently drops packets to `[::1]:60000` or a different port if
// specified through `TEST_ELECTRUM_TIMEOUT_PORT`
//
// this can be setup with: ip6tables -I INPUT 1 -p tcp -d ::1 --dport 60000 -j DROP
// and removed with: ip6tables -D INPUT -p tcp -d ::1 --dport 60000 -j DROP
//
// The test tries to create a client to `localhost` and expects it to succeed, but only
// after at least 2 seconds have passed which is roughly the timeout time for the first
// try.

use std::net::TcpListener;
use std::sync::mpsc::channel;
use std::time::{Duration, Instant};

let endpoint =
std::env::var("TEST_ELECTRUM_TIMEOUT_PORT").unwrap_or("localhost:60000".into());
let (sender, receiver) = channel();

std::thread::spawn(move || {
let listener = TcpListener::bind("127.0.0.1:60000").unwrap();
sender.send(()).unwrap();

for _stream in listener.incoming() {
std::thread::sleep(Duration::from_secs(60))
}
});

receiver
.recv_timeout(Duration::from_secs(5))
.expect("Can't start local listener");

let now = Instant::now();
let client = Client::from_config(
&endpoint,
crate::config::ConfigBuilder::new()
.timeout(Some(Duration::from_secs(5)))
.build(),
);
let elapsed = now.elapsed();

assert!(client.is_ok());
assert!(elapsed > Duration::from_secs(2));
}
}
Loading
Loading