Compare commits

..

No commits in common. "master" and "0.22.0" have entirely different histories.

15 changed files with 105 additions and 697 deletions

View File

@ -5,19 +5,19 @@ name: CI
jobs:
test:
name: Test
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
env:
TEST_ELECTRUM_SERVER: electrum.blockstream.info:50001
strategy:
matrix:
rust:
- stable # STABLE
- 1.75.0 # MSRV
- 1.63.0 # MSRV
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
@ -28,6 +28,10 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
- name: Pin dependencies for MSRV
if: matrix.rust == '1.63.0'
run: |
cargo update -p rustls --precise "0.23.17"
- name: Test
run: cargo test --verbose --all-features
- name: Setup iptables for the timeout test
@ -41,7 +45,6 @@ jobs:
- run: cargo check --verbose --no-default-features --features=proxy,use-openssl
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls-ring
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls,use-rustls-ring
fmt:
name: Rust fmt
@ -58,14 +61,16 @@ jobs:
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
clippy_check:
name: Rust clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.90.0
toolchain: 1.78.0
components: clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2.2.1
- run: cargo clippy --all-features --all-targets -- -D warnings
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all-targets -- -D warnings

View File

@ -9,22 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.24.1]
- Default to `ring` if multiple `rustls` features are set #183
## [0.24.0]
- Use default `CryptoProvider` if available, otherwise install `rustls`'s `CryptoProvider` based on features #171
- Add a new batch method for `blockchain.transaction.get_merkle` #170
## [0.23.1]
- Fix batch request to Electrum servers out of order responses #160
- Allow types that references to `ElectrumApi` to also implement it #163
## [0.23.0]
- Raise MSRV to `1.75` and bump `rustls` to `0.23.21` #159
- Enforce min `rustls` version 0.23.19 to support MSRV with fix for RUSTSEC-2024-0399 #158
## [0.22.0]
- Updates the NoCertificateVerification implementation for the rustls::client::danger::ServerCertVerifier to use the rustls::SignatureScheme from CryptoProvider in use #150
@ -55,12 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Revert "errors if expecting headers notification but not subscribed" #115
[0.18.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.17.0...0.18.0
[0.19.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.18.0...0.19.0
[0.20.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.19.0...0.20.0
[0.21.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.20.0...0.21.0
[0.22.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.21.0...0.22.0
[0.23.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.22.0...0.23.0
[0.23.1]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.23.0...0.23.1
[0.24.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.23.1...0.24.0
[0.24.1]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.24.0...0.24.1
[Unreleased]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.24.1...HEAD
[0.19.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.18.0...v0.19.0
[0.20.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.19.0...v0.20.0
[0.21.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.20.0...v0.21.0
[0.22.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.21.0...v0.22.0
[Unreleased]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.22.0...HEAD

View File

@ -1,6 +1,6 @@
[package]
name = "electrum-client"
version = "0.24.1"
version = "0.22.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
license = "MIT"
homepage = "https://github.com/bitcoindevkit/rust-electrum-client"
@ -9,7 +9,7 @@ documentation = "https://docs.rs/electrum-client/"
description = "Bitcoin Electrum client library. Supports plaintext, TLS and Onion servers."
keywords = ["bitcoin", "electrum"]
readme = "README.md"
rust-version = "1.75.0"
rust-version = "1.63.0"
edition = "2021"
# loosely based on https://github.com/evgeniy-scherbina/rust-electrumx-client
@ -26,7 +26,7 @@ serde_json = { version = "^1.0" }
# Optional dependencies
openssl = { version = "0.10", optional = true }
rustls = { version = "0.23.21", optional = true, default-features = false }
rustls = { version = "0.23", optional = true, default-features = false }
webpki-roots = { version = "0.25", optional = true }
byteorder = { version = "1.0", optional = true }

View File

@ -5,11 +5,18 @@
[GitHub Workflow]: https://github.com/bitcoindevkit/rust-electrum-client/actions?query=workflow%3ACI
[Latest Version]: https://img.shields.io/crates/v/electrum-client.svg
[crates.io]: https://crates.io/crates/electrum-client
[MSRV Badge]: https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg
[Rust Blog]: https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html
[MSRV Badge]: https://img.shields.io/badge/rustc-1.63.0%2B-lightgrey.svg
[Rust Blog]: https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html
Bitcoin Electrum client library. Supports plaintext, TLS and Onion servers.
## Minimum Supported Rust Version (MSRV)
This library should compile with any combination of features with Rust 1.75.0.
This library should compile with any combination of features with Rust 1.63.0.
To build with the MSRV you will need to pin dependencies as follows:
```shell
cargo update -p rustls --precise "0.23.17"
```

View File

@ -1 +1 @@
msrv="1.75.0"
msrv="1.63.0"

View File

@ -1,34 +0,0 @@
alias b := build
alias c := check
alias f := fmt
alias t := test
alias p := pre-push
_default:
@just --list
# Build the project
build:
cargo build
# Check code: formatting, compilation, linting, doc comments, and commit signature
check:
cargo +nightly fmt --all -- --check
cargo check --all-features --all-targets
cargo clippy --all-features --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps
@[ "$(git log --pretty='format:%G?' -1 HEAD)" = "N" ] && \
echo "\n⚠ Unsigned commit: BDK requires that commits be signed." || \
true
# Format all code
fmt:
cargo +nightly fmt
# Run all tests on the workspace with all features
test:
cargo test --all-features -- --test-threads=1
# Run pre-push suite: format, check, and test
pre-push: fmt check test

View File

@ -2,7 +2,6 @@
use std::borrow::Borrow;
use std::convert::TryInto;
use std::ops::Deref;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::{block, Script, Transaction, Txid};
@ -10,178 +9,6 @@ use bitcoin::{block, Script, Transaction, Txid};
use crate::batch::Batch;
use crate::types::*;
impl<E: Deref> ElectrumApi for E
where
E::Target: ElectrumApi,
{
fn raw_call(
&self,
method_name: &str,
params: impl IntoIterator<Item = Param>,
) -> Result<serde_json::Value, Error> {
(**self).raw_call(method_name, params)
}
fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error> {
(**self).batch_call(batch)
}
fn block_headers_subscribe_raw(&self) -> Result<RawHeaderNotification, Error> {
(**self).block_headers_subscribe_raw()
}
fn block_headers_pop_raw(&self) -> Result<Option<RawHeaderNotification>, Error> {
(**self).block_headers_pop_raw()
}
fn block_header_raw(&self, height: usize) -> Result<Vec<u8>, Error> {
(**self).block_header_raw(height)
}
fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error> {
(**self).block_headers(start_height, count)
}
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
(**self).estimate_fee(number)
}
fn relay_fee(&self) -> Result<f64, Error> {
(**self).relay_fee()
}
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
(**self).script_subscribe(script)
}
fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<&'s Script>,
{
(**self).batch_script_subscribe(scripts)
}
fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error> {
(**self).script_unsubscribe(script)
}
fn script_pop(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
(**self).script_pop(script)
}
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes, Error> {
(**self).script_get_balance(script)
}
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<&'s Script>,
{
(**self).batch_script_get_balance(scripts)
}
fn script_get_history(&self, script: &Script) -> Result<Vec<GetHistoryRes>, Error> {
(**self).script_get_history(script)
}
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<&'s Script>,
{
(**self).batch_script_get_history(scripts)
}
fn script_list_unspent(&self, script: &Script) -> Result<Vec<ListUnspentRes>, Error> {
(**self).script_list_unspent(script)
}
fn batch_script_list_unspent<'s, I>(
&self,
scripts: I,
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<&'s Script>,
{
(**self).batch_script_list_unspent(scripts)
}
fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error> {
(**self).transaction_get_raw(txid)
}
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<&'t Txid>,
{
(**self).batch_transaction_get_raw(txids)
}
fn batch_block_header_raw<I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<u32>,
{
(**self).batch_block_header_raw(heights)
}
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<usize>,
{
(**self).batch_estimate_fee(numbers)
}
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
(**self).transaction_broadcast_raw(raw_tx)
}
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
(**self).transaction_get_merkle(txid, height)
}
fn batch_transaction_get_merkle<I>(
&self,
txids_and_heights: I,
) -> Result<Vec<GetMerkleRes>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<(Txid, usize)>,
{
(**self).batch_transaction_get_merkle(txids_and_heights)
}
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
(**self).txid_from_pos(height, tx_pos)
}
fn txid_from_pos_with_merkle(
&self,
height: usize,
tx_pos: usize,
) -> Result<TxidFromPosRes, Error> {
(**self).txid_from_pos_with_merkle(height, tx_pos)
}
fn server_features(&self) -> Result<ServerFeaturesRes, Error> {
(**self).server_features()
}
fn ping(&self) -> Result<(), Error> {
(**self).ping()
}
#[cfg(feature = "debug-calls")]
fn calls_made(&self) -> Result<usize, Error> {
(**self).calls_made()
}
}
/// API calls exposed by an Electrum client
pub trait ElectrumApi {
/// Gets the block header for height `height`.
@ -373,17 +200,6 @@ pub trait ElectrumApi {
/// 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>;
/// Batch version of [`transaction_get_merkle`](#method.transaction_get_merkle).
///
/// Take a list of `(txid, height)`, for transactions with `txid` confirmed in the block at `height`.
fn batch_transaction_get_merkle<I>(
&self,
txids_and_heights: I,
) -> Result<Vec<GetMerkleRes>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<(Txid, usize)>;
/// Returns a transaction hash, given a block `height` and a `tx_pos` in the block.
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error>;
@ -406,225 +222,3 @@ pub trait ElectrumApi {
/// Returns the number of network calls made since the creation of the client.
fn calls_made(&self) -> Result<usize, Error>;
}
#[cfg(test)]
mod test {
use std::{borrow::Cow, sync::Arc};
use super::ElectrumApi;
#[derive(Debug, Clone)]
struct FakeApi;
impl ElectrumApi for FakeApi {
fn raw_call(
&self,
_: &str,
_: impl IntoIterator<Item = super::Param>,
) -> Result<serde_json::Value, super::Error> {
unreachable!()
}
fn batch_call(&self, _: &crate::Batch) -> Result<Vec<serde_json::Value>, super::Error> {
unreachable!()
}
fn block_headers_subscribe_raw(
&self,
) -> Result<super::RawHeaderNotification, super::Error> {
unreachable!()
}
fn block_headers_pop_raw(
&self,
) -> Result<Option<super::RawHeaderNotification>, super::Error> {
unreachable!()
}
fn block_header_raw(&self, _: usize) -> Result<Vec<u8>, super::Error> {
unreachable!()
}
fn block_headers(&self, _: usize, _: usize) -> Result<super::GetHeadersRes, super::Error> {
unreachable!()
}
fn estimate_fee(&self, _: usize) -> Result<f64, super::Error> {
unreachable!()
}
fn relay_fee(&self) -> Result<f64, super::Error> {
unreachable!()
}
fn script_subscribe(
&self,
_: &bitcoin::Script,
) -> Result<Option<super::ScriptStatus>, super::Error> {
unreachable!()
}
fn batch_script_subscribe<'s, I>(
&self,
_: I,
) -> Result<Vec<Option<super::ScriptStatus>>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<&'s bitcoin::Script>,
{
unreachable!()
}
fn script_unsubscribe(&self, _: &bitcoin::Script) -> Result<bool, super::Error> {
unreachable!()
}
fn script_pop(
&self,
_: &bitcoin::Script,
) -> Result<Option<super::ScriptStatus>, super::Error> {
unreachable!()
}
fn script_get_balance(
&self,
_: &bitcoin::Script,
) -> Result<super::GetBalanceRes, super::Error> {
unreachable!()
}
fn batch_script_get_balance<'s, I>(
&self,
_: I,
) -> Result<Vec<super::GetBalanceRes>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<&'s bitcoin::Script>,
{
unreachable!()
}
fn script_get_history(
&self,
_: &bitcoin::Script,
) -> Result<Vec<super::GetHistoryRes>, super::Error> {
unreachable!()
}
fn batch_script_get_history<'s, I>(
&self,
_: I,
) -> Result<Vec<Vec<super::GetHistoryRes>>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<&'s bitcoin::Script>,
{
unreachable!()
}
fn script_list_unspent(
&self,
_: &bitcoin::Script,
) -> Result<Vec<super::ListUnspentRes>, super::Error> {
unreachable!()
}
fn batch_script_list_unspent<'s, I>(
&self,
_: I,
) -> Result<Vec<Vec<super::ListUnspentRes>>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<&'s bitcoin::Script>,
{
unreachable!()
}
fn transaction_get_raw(&self, _: &bitcoin::Txid) -> Result<Vec<u8>, super::Error> {
unreachable!()
}
fn batch_transaction_get_raw<'t, I>(&self, _: I) -> Result<Vec<Vec<u8>>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<&'t bitcoin::Txid>,
{
unreachable!()
}
fn batch_block_header_raw<I>(&self, _: I) -> Result<Vec<Vec<u8>>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<u32>,
{
unreachable!()
}
fn batch_estimate_fee<I>(&self, _: I) -> Result<Vec<f64>, super::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<usize>,
{
unreachable!()
}
fn transaction_broadcast_raw(&self, _: &[u8]) -> Result<bitcoin::Txid, super::Error> {
unreachable!()
}
fn transaction_get_merkle(
&self,
_: &bitcoin::Txid,
_: usize,
) -> Result<super::GetMerkleRes, super::Error> {
unreachable!()
}
fn batch_transaction_get_merkle<I>(
&self,
_: I,
) -> Result<Vec<crate::GetMerkleRes>, crate::Error>
where
I: IntoIterator + Clone,
I::Item: std::borrow::Borrow<(bitcoin::Txid, usize)>,
{
unreachable!()
}
fn txid_from_pos(&self, _: usize, _: usize) -> Result<bitcoin::Txid, super::Error> {
unreachable!()
}
fn txid_from_pos_with_merkle(
&self,
_: usize,
_: usize,
) -> Result<super::TxidFromPosRes, super::Error> {
unreachable!()
}
fn server_features(&self) -> Result<super::ServerFeaturesRes, super::Error> {
unreachable!()
}
fn ping(&self) -> Result<(), super::Error> {
unreachable!()
}
#[cfg(feature = "debug-calls")]
fn calls_made(&self) -> Result<usize, super::Error> {
unreachable!()
}
}
fn is_impl<A: ElectrumApi>() {}
#[test]
fn deref() {
is_impl::<FakeApi>();
is_impl::<&FakeApi>();
is_impl::<Arc<FakeApi>>();
is_impl::<Box<FakeApi>>();
is_impl::<Cow<FakeApi>>();
}
}

View File

@ -62,17 +62,6 @@ impl Batch {
.push((String::from("blockchain.transaction.get"), params));
}
/// Add one `blockchain.transaction.get_merkle` request to the batch queue
pub fn transaction_get_merkle(&mut self, tx_hash_and_height: &(Txid, usize)) {
let (tx_hash, height) = tx_hash_and_height;
let params = vec![
Param::String(format!("{:x}", tx_hash)),
Param::Usize(*height),
];
self.calls
.push((String::from("blockchain.transaction.get_merkle"), params));
}
/// Add one `blockchain.estimatefee` request to the batch queue
pub fn estimate_fee(&mut self, number: usize) {
let params = vec![Param::Usize(number)];
@ -88,7 +77,7 @@ impl Batch {
}
/// Returns an iterator on the batch
pub fn iter(&self) -> BatchIter<'_> {
pub fn iter(&self) -> BatchIter {
BatchIter {
batch: self,
index: 0,

View File

@ -327,22 +327,6 @@ impl ElectrumApi for Client {
impl_inner_call!(self, transaction_get_merkle, txid, height)
}
#[inline]
fn batch_transaction_get_merkle<I>(
&self,
txids_and_heights: I,
) -> Result<Vec<GetMerkleRes>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<(Txid, usize)>,
{
impl_inner_call!(
self,
batch_transaction_get_merkle,
txids_and_heights.clone()
)
}
#[inline]
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
impl_inner_call!(self, txid_from_pos, height, tx_pos)
@ -448,9 +432,7 @@ mod tests {
let now = Instant::now();
let client = Client::from_config(
&endpoint,
crate::config::ConfigBuilder::new()
.timeout(Some(Duration::from_secs(5)))
.build(),
crate::config::ConfigBuilder::new().timeout(Some(5)).build(),
);
let elapsed = now.elapsed();

View File

@ -55,8 +55,8 @@ impl ConfigBuilder {
}
/// Sets the timeout
pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
self.config.timeout = timeout;
pub fn timeout(mut self, timeout: Option<u8>) -> Self {
self.config.timeout = timeout.map(|t| Duration::from_secs(t as u64));
self
}

View File

@ -3,7 +3,7 @@
//! This module contains the definition of the raw client that wraps the transport method
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
use std::io::{BufRead, BufReader, Read, Write};
use std::mem::drop;
use std::net::{TcpStream, ToSocketAddrs};
@ -406,29 +406,6 @@ impl RawClient<ElectrumSslStream> {
) -> Result<Self, Error> {
use std::convert::TryFrom;
if rustls::crypto::CryptoProvider::get_default().is_none() {
// We install a crypto provider depending on the set feature.
#[cfg(all(feature = "use-rustls", not(feature = "use-rustls-ring")))]
rustls::crypto::CryptoProvider::install_default(
rustls::crypto::aws_lc_rs::default_provider(),
)
.map_err(|_| {
Error::CouldNotCreateConnection(rustls::Error::General(
"Failed to install CryptoProvider".to_string(),
))
})?;
#[cfg(feature = "use-rustls-ring")]
rustls::crypto::CryptoProvider::install_default(
rustls::crypto::ring::default_provider(),
)
.map_err(|_| {
Error::CouldNotCreateConnection(rustls::Error::General(
"Failed to install CryptoProvider".to_string(),
))
})?;
}
let builder = ClientConfig::builder();
let config = if validate_domain {
@ -449,7 +426,7 @@ impl RawClient<ElectrumSslStream> {
builder
.dangerous()
.with_custom_certificate_verifier(std::sync::Arc::new(
#[cfg(all(feature = "use-rustls", not(feature = "use-rustls-ring")))]
#[cfg(feature = "use-rustls")]
danger::NoCertificateVerification::new(rustls::crypto::aws_lc_rs::default_provider()),
#[cfg(feature = "use-rustls-ring")]
danger::NoCertificateVerification::new(rustls::crypto::ring::default_provider()),
@ -562,10 +539,11 @@ impl<S: Read + Write> RawClient<S> {
if let Some(until_message) = until_message {
// If we are trying to start a reader thread but the corresponding sender is
// missing from the map, exit immediately. We might have already received a
// response for that id, but we don't know it yet. Exiting here forces the
// calling code to fallback to the sender-receiver method, and it should find
// a message there waiting for it.
// missing from the map, exit immediately. This can happen with batch calls,
// since the sender is shared for all the individual queries in a call. We
// might have already received a response for that id, but we don't know it
// yet. Exiting here forces the calling code to fallback to the sender-receiver
// method, and it should find a message there waiting for it.
if self.waiting_map.lock()?.get(&until_message).is_none() {
return Err(Error::CouldntLockReader);
}
@ -784,10 +762,12 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error> {
let mut raw = Vec::new();
let mut missing_responses = Vec::new();
let mut missing_responses = BTreeSet::new();
let mut answers = BTreeMap::new();
// Add our listener to the map before we send the request
// Add our listener to the map before we send the request, Here we will clone the sender
// for every request id, so that we only have to monitor one receiver.
let (sender, receiver) = channel();
for (method, params) in batch.iter() {
let req = Request::new_id(
@ -795,12 +775,9 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
method,
params.to_vec(),
);
// Add distinct channel to each request so when we remove our request id (and sender) from the waiting_map
// we can be sure that the response gets sent to the correct channel in self.recv
let (sender, receiver) = channel();
missing_responses.push((req.id, receiver));
missing_responses.insert(req.id);
self.waiting_map.lock()?.insert(req.id, sender);
self.waiting_map.lock()?.insert(req.id, sender.clone());
raw.append(&mut serde_json::to_vec(&req)?);
raw.extend_from_slice(b"\n");
@ -819,8 +796,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
self.increment_calls();
for (req_id, receiver) in missing_responses.iter() {
match self.recv(receiver, *req_id) {
for req_id in missing_responses.iter() {
match self.recv(&receiver, *req_id) {
Ok(mut resp) => answers.insert(req_id, resp["result"].take()),
Err(e) => {
// In case of error our sender could still be left in the map, depending on where
@ -828,7 +805,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
warn!("got error for req_id {}: {:?}", req_id, e);
warn!("removing all waiting req of this batch");
let mut guard = self.waiting_map.lock()?;
for (req_id, _) in missing_responses.iter() {
for req_id in missing_responses.iter() {
guard.remove(req_id);
}
return Err(e);
@ -1125,17 +1102,6 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
Ok(serde_json::from_value(result)?)
}
fn batch_transaction_get_merkle<I>(
&self,
txids_and_heights: I,
) -> Result<Vec<GetMerkleRes>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<(Txid, usize)>,
{
impl_batch_call!(self, txids_and_heights, transaction_get_merkle)
}
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
let params = vec![Param::Usize(height), Param::Usize(tx_pos)];
let req = Request::new_id(
@ -1224,32 +1190,12 @@ mod test {
assert_eq!(resp.hash_function, Some("sha256".into()));
assert_eq!(resp.pruning, None);
}
#[test]
#[ignore = "depends on a live server"]
fn test_batch_response_ordering() {
// The electrum.blockstream.info:50001 node always sends back ordered responses which will make this always pass.
// However, many servers do not, so we use one of those servers for this test.
let client = RawClient::new("exs.dyshek.org:50001", None).unwrap();
let heights: Vec<u32> = vec![1, 4, 8, 12, 222, 6666, 12];
let result_times = [
1231469665, 1231470988, 1231472743, 1231474888, 1231770653, 1236456633, 1231474888,
];
// Check ordering 10 times. This usually fails within 5 if ordering is incorrect.
for _ in 0..10 {
let results = client.batch_block_header(&heights).unwrap();
for (index, result) in results.iter().enumerate() {
assert_eq!(result_times[index], result.time);
}
}
}
#[test]
fn test_relay_fee() {
let client = RawClient::new(get_test_server(), None).unwrap();
let resp = client.relay_fee().unwrap();
assert!(resp > 0.0);
assert_eq!(resp, 0.00001);
}
#[test]
@ -1482,98 +1428,6 @@ mod test {
));
}
#[test]
fn test_batch_transaction_get_merkle() {
use bitcoin::Txid;
struct TestCase {
txid: Txid,
block_height: usize,
exp_pos: usize,
exp_bytes: [u8; 32],
}
let client = RawClient::new(get_test_server(), None).unwrap();
let test_cases: Vec<TestCase> = vec![
TestCase {
txid: Txid::from_str(
"1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d",
)
.unwrap(),
block_height: 630000,
exp_pos: 68,
exp_bytes: [
34, 65, 51, 64, 49, 139, 115, 189, 185, 246, 70, 225, 168, 193, 217, 195, 47,
66, 179, 240, 153, 24, 114, 215, 144, 196, 212, 41, 39, 155, 246, 25,
],
},
TestCase {
txid: Txid::from_str(
"70a8639bc9b743c0610d1231103a2f8e99f4a25670946b91f16c55a5373b37d1",
)
.unwrap(),
block_height: 630001,
exp_pos: 25,
exp_bytes: [
169, 100, 34, 99, 168, 101, 25, 168, 184, 90, 77, 50, 151, 245, 130, 101, 193,
229, 136, 128, 63, 110, 241, 19, 242, 59, 184, 137, 245, 249, 188, 110,
],
},
TestCase {
txid: Txid::from_str(
"a0db149ace545beabbd87a8d6b20ffd6aa3b5a50e58add49a3d435f898c272cf",
)
.unwrap(),
block_height: 840000,
exp_pos: 0,
exp_bytes: [
43, 184, 95, 75, 0, 75, 230, 218, 84, 247, 102, 193, 124, 30, 133, 81, 135, 50,
113, 18, 194, 49, 239, 47, 243, 94, 186, 208, 234, 103, 198, 158,
],
},
];
let txids_and_heights: Vec<(Txid, usize)> = test_cases
.iter()
.map(|case| (case.txid, case.block_height))
.collect();
let resp = client
.batch_transaction_get_merkle(&txids_and_heights)
.unwrap();
for (i, (res, test_case)) in resp.iter().zip(test_cases).enumerate() {
assert_eq!(res.block_height, test_case.block_height);
assert_eq!(res.pos, test_case.exp_pos);
assert_eq!(res.merkle.len(), 12);
assert_eq!(res.merkle[0], test_case.exp_bytes);
// Check we can verify the merkle proof validity, but fail if we supply wrong data.
let block_header = client.block_header(res.block_height).unwrap();
assert!(utils::validate_merkle_proof(
&txids_and_heights[i].0,
&block_header.merkle_root,
res
));
let mut fail_res = res.clone();
fail_res.pos = 13;
assert!(!utils::validate_merkle_proof(
&txids_and_heights[i].0,
&block_header.merkle_root,
&fail_res
));
let fail_block_header = client.block_header(res.block_height + 1).unwrap();
assert!(!utils::validate_merkle_proof(
&txids_and_heights[i].0,
&fail_block_header.merkle_root,
res
));
}
}
#[test]
fn test_txid_from_pos() {
use bitcoin::Txid;

View File

@ -99,7 +99,7 @@ impl ToTargetAddr for (Ipv6Addr, u16) {
}
}
impl ToTargetAddr for (&str, u16) {
impl<'a> ToTargetAddr for (&'a str, u16) {
fn to_target_addr(&self) -> io::Result<TargetAddr> {
// try to parse as an IP first
if let Ok(addr) = self.0.parse::<Ipv4Addr>() {
@ -114,7 +114,7 @@ impl ToTargetAddr for (&str, u16) {
}
}
impl ToTargetAddr for &str {
impl<'a> ToTargetAddr for &'a str {
fn to_target_addr(&self) -> io::Result<TargetAddr> {
// try to parse as an IP first
if let Ok(addr) = self.parse::<SocketAddrV4>() {

View File

@ -18,7 +18,12 @@ fn read_response(socket: &mut TcpStream) -> io::Result<SocketAddrV4> {
match response.read_u8()? {
90 => {}
91 => return Err(io::Error::other("request rejected or failed")),
91 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"request rejected or failed",
))
}
92 => {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
@ -143,7 +148,7 @@ impl Read for Socks4Stream {
}
}
impl Read for &Socks4Stream {
impl<'a> Read for &'a Socks4Stream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(&self.socket).read(buf)
}
@ -159,7 +164,7 @@ impl Write for Socks4Stream {
}
}
impl Write for &Socks4Stream {
impl<'a> Write for &'a Socks4Stream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(&self.socket).write(buf)
}

View File

@ -37,7 +37,10 @@ fn read_addr<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
ip, port, 0, 0,
))))
}
_ => Err(io::Error::other("unsupported address type")),
_ => Err(io::Error::new(
io::ErrorKind::Other,
"unsupported address type",
)),
}
}
@ -51,15 +54,35 @@ fn read_response(socket: &mut TcpStream) -> io::Result<TargetAddr> {
match socket.read_u8()? {
0 => {}
1 => return Err(io::Error::other("general SOCKS server failure")),
2 => return Err(io::Error::other("connection not allowed by ruleset")),
3 => return Err(io::Error::other("network unreachable")),
4 => return Err(io::Error::other("host unreachable")),
5 => return Err(io::Error::other("connection refused")),
6 => return Err(io::Error::other("TTL expired")),
7 => return Err(io::Error::other("command not supported")),
8 => return Err(io::Error::other("address kind not supported")),
_ => return Err(io::Error::other("unknown error")),
1 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"general SOCKS server failure",
))
}
2 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"connection not allowed by ruleset",
))
}
3 => return Err(io::Error::new(io::ErrorKind::Other, "network unreachable")),
4 => return Err(io::Error::new(io::ErrorKind::Other, "host unreachable")),
5 => return Err(io::Error::new(io::ErrorKind::Other, "connection refused")),
6 => return Err(io::Error::new(io::ErrorKind::Other, "TTL expired")),
7 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"command not supported",
))
}
8 => {
return Err(io::Error::new(
io::ErrorKind::Other,
"address kind not supported",
))
}
_ => return Err(io::Error::new(io::ErrorKind::Other, "unknown error")),
}
if socket.read_u8()? != 0 {
@ -112,7 +135,7 @@ enum Authentication<'a> {
None,
}
impl Authentication<'_> {
impl<'a> Authentication<'a> {
fn id(&self) -> u8 {
match *self {
Authentication::Password { .. } => 2,
@ -204,11 +227,14 @@ impl Socks5Stream {
}
if selected_method == 0xff {
return Err(io::Error::other("no acceptable auth methods"));
return Err(io::Error::new(
io::ErrorKind::Other,
"no acceptable auth methods",
));
}
if selected_method != auth.id() && selected_method != Authentication::None.id() {
return Err(io::Error::other("unknown auth method"));
return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method"));
}
match *auth {
@ -303,7 +329,7 @@ impl Read for Socks5Stream {
}
}
impl Read for &Socks5Stream {
impl<'a> Read for &'a Socks5Stream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
(&self.socket).read(buf)
}
@ -319,7 +345,7 @@ impl Write for Socks5Stream {
}
}
impl Write for &Socks5Stream {
impl<'a> Write for &'a Socks5Stream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(&self.socket).write(buf)
}

View File

@ -13,7 +13,7 @@ use bitcoin::Txid;
/// otherwise.
///
/// [`transaction_get_merkle`]: crate::ElectrumApi::transaction_get_merkle
/// [`BlockHeader`]: bitcoin::block::Header
/// [`BlockHeader`]: bitcoin::BlockHeader
pub fn validate_merkle_proof(
txid: &Txid,
merkle_root: &TxMerkleNode,