Compare commits

..

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

17 changed files with 206 additions and 1015 deletions

View File

@ -3,31 +3,32 @@ on: [push, pull_request]
name: CI
jobs:
test:
test-fmt:
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
#TEST_ELECTRUM_SERVER: bitcoin.aranguren.org:50001
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Cache
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Install rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
- name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain
run: $HOME/.cargo/bin/rustup default stable
- name: Set profile
run: $HOME/.cargo/bin/rustup set profile minimal
- name: Fmt
run: cargo fmt -- --check --verbose
- name: Test
run: cargo test --verbose --all-features
- name: Setup iptables for the timeout test
@ -40,32 +41,3 @@ jobs:
- run: cargo check --verbose --no-default-features --features=minimal,debug-calls
- 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
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- name: Check fmt
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
with:
toolchain: 1.90.0
components: clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2.2.1
- run: cargo clippy --all-features --all-targets -- -D warnings

View File

@ -9,39 +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
- Add `id_from_pos` support #155
## [0.21.0]
- Add use-rustls-ring feature #135
- refactor: make validate_merkle_proof more efficient #134
- chore: set rust edition to 2021, fix clippy, add ci fmt and clippy checks #139
## [0.20.0]
- Upgrade rustls to 0.23 #132
- chore(deps): upgrade rust-bitcoin to 0.32.0 #133
- ci: add test with MSRV 1.63.0 #128
## [0.19.0]
- Add Batch::raw and improve docs #94
@ -55,12 +22,5 @@ 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
[Unreleased]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.19.0...HEAD

View File

@ -1,16 +1,14 @@
[package]
name = "electrum-client"
version = "0.24.1"
version = "0.19.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
license = "MIT"
homepage = "https://github.com/bitcoindevkit/rust-electrum-client"
repository = "https://github.com/bitcoindevkit/rust-electrum-client"
homepage = "https://github.com/MagicalBitcoin/rust-electrum-client"
repository = "https://github.com/MagicalBitcoin/rust-electrum-client"
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"
edition = "2021"
# loosely based on https://github.com/evgeniy-scherbina/rust-electrumx-client
@ -20,13 +18,13 @@ path = "src/lib.rs"
[dependencies]
log = "^0.4"
bitcoin = { version = "0.32", features = ["serde"] }
bitcoin = { version = "0.31.0", features = ["serde"] }
serde = { version = "^1.0", features = ["derive"] }
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.21", optional = true, features = ["dangerous_configuration"] }
webpki-roots = { version = "0.25", optional = true }
byteorder = { version = "1.0", optional = true }
@ -42,6 +40,5 @@ default = ["proxy", "use-rustls"]
minimal = []
debug-calls = []
proxy = ["byteorder", "winapi", "libc"]
use-rustls = ["webpki-roots", "rustls/default"]
use-rustls-ring = ["webpki-roots", "rustls/ring", "rustls/logging", "rustls/std", "rustls/tls12"]
use-rustls = ["webpki-roots", "rustls"]
use-openssl = ["openssl"]

View File

@ -1,15 +1,8 @@
# rust-electrum-client
[![Build Status]][GitHub Workflow] [![Latest Version]][crates.io] [![MSRV Badge]][Rust Blog]
# rust-electrum-client [![Build Status]][GitHub Workflow] [![Latest Version]][crates.io]
[Build Status]: https://github.com/bitcoindevkit/rust-electrum-client/actions/workflows/cont_integration.yml/badge.svg
[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
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.

View File

@ -1 +0,0 @@
msrv="1.75.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,185 +2,12 @@
use std::borrow::Borrow;
use std::convert::TryInto;
use std::ops::Deref;
use bitcoin::consensus::encode::{deserialize, serialize};
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()
}
}
use batch::Batch;
use types::*;
/// API calls exposed by an Electrum client
pub trait ElectrumApi {
@ -373,28 +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>;
/// Returns a transaction hash and a merkle path, given a block `height` and a `tx_pos` in the
/// block.
fn txid_from_pos_with_merkle(
&self,
height: usize,
tx_pos: usize,
) -> Result<TxidFromPosRes, Error>;
/// Returns the capabilities of the server.
fn server_features(&self) -> Result<ServerFeaturesRes, Error>;
@ -406,225 +211,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

@ -4,7 +4,7 @@
use bitcoin::{Script, Txid};
use crate::types::{Call, Param, ToElectrumScriptHash};
use types::{Call, Param, ToElectrumScriptHash};
/// Helper structure that caches all the requests before they are actually sent to the server.
///
@ -16,7 +16,6 @@ use crate::types::{Call, Param, ToElectrumScriptHash};
/// [`Client`](../client/struct.Client.html), like
/// [`batch_script_get_balance`](../client/struct.Client.html#method.batch_script_get_balance) to ask the
/// server for the balance of multiple scripts with a single request.
#[derive(Default)]
pub struct Batch {
calls: Vec<Call>,
}
@ -62,17 +61,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 +76,7 @@ impl Batch {
}
/// Returns an iterator on the batch
pub fn iter(&self) -> BatchIter<'_> {
pub fn iter(&self) -> BatchIter {
BatchIter {
batch: self,
index: 0,
@ -119,3 +107,9 @@ impl<'a> std::iter::Iterator for BatchIter<'a> {
val
}
}
impl std::default::Default for Batch {
fn default() -> Self {
Batch { calls: Vec::new() }
}
}

View File

@ -6,12 +6,12 @@ use log::{info, warn};
use bitcoin::{Script, Txid};
use crate::api::ElectrumApi;
use crate::batch::Batch;
use crate::config::Config;
use crate::raw_client::*;
use crate::types::*;
use api::ElectrumApi;
use batch::Batch;
use config::Config;
use raw_client::*;
use std::convert::TryFrom;
use types::*;
/// Generalized Electrum client that supports multiple backends. This wraps
/// [`RawClient`](client/struct.RawClient.html) and provides a more user-friendly
@ -148,6 +148,7 @@ impl Client {
/// If no prefix is specified, then `tcp://` is assumed.
///
/// See [Client::from_config] for more configuration options
///
pub fn new(url: &str) -> Result<Self, Error> {
Self::from_config(url, Config::default())
}
@ -327,36 +328,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)
}
#[inline]
fn txid_from_pos_with_merkle(
&self,
height: usize,
tx_pos: usize,
) -> Result<TxidFromPosRes, Error> {
impl_inner_call!(self, txid_from_pos_with_merkle, height, tx_pos)
}
#[inline]
fn server_features(&self) -> Result<ServerFeaturesRes, Error> {
impl_inner_call!(self, server_features)
@ -382,7 +353,7 @@ mod tests {
fn more_failed_attempts_than_retries_means_exhausted() {
let exhausted = retries_exhausted(10, 5);
assert!(exhausted)
assert_eq!(exhausted, true)
}
#[test]
@ -391,21 +362,21 @@ mod tests {
let exhausted = retries_exhausted(failed_attempts, u8::MAX);
assert!(exhausted)
assert_eq!(exhausted, true)
}
#[test]
fn less_failed_attempts_means_not_exhausted() {
let exhausted = retries_exhausted(2, 5);
assert!(!exhausted)
assert_eq!(exhausted, false)
}
#[test]
fn attempts_equals_retries_means_not_exhausted_yet() {
let exhausted = retries_exhausted(2, 2);
assert!(!exhausted)
assert_eq!(exhausted, false)
}
#[test]
@ -437,7 +408,7 @@ mod tests {
sender.send(()).unwrap();
for _stream in listener.incoming() {
std::thread::sleep(Duration::from_secs(60))
loop {}
}
});
@ -448,9 +419,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

@ -25,22 +25,14 @@ extern crate log;
#[cfg(feature = "use-openssl")]
extern crate openssl;
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "default", feature = "use-rustls"),
not(feature = "use-openssl")
))]
extern crate rustls;
extern crate serde;
extern crate serde_json;
#[cfg(any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
))]
#[cfg(any(feature = "use-rustls", feature = "default"))]
extern crate webpki_roots;
#[cfg(any(feature = "default", feature = "proxy"))]
@ -59,8 +51,7 @@ mod batch;
#[cfg(any(
all(feature = "proxy", feature = "use-openssl"),
all(feature = "proxy", feature = "use-rustls"),
all(feature = "proxy", feature = "use-rustls-ring")
all(feature = "proxy", feature = "use-rustls")
))]
pub mod client;
@ -75,8 +66,7 @@ pub use api::ElectrumApi;
pub use batch::Batch;
#[cfg(any(
all(feature = "proxy", feature = "use-openssl"),
all(feature = "proxy", feature = "use-rustls"),
all(feature = "proxy", feature = "use-rustls-ring")
all(feature = "proxy", feature = "use-rustls")
))]
pub use client::*;
pub use config::{Config, ConfigBuilder, Socks5Config};

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};
@ -21,29 +21,22 @@ use bitcoin::{Script, Txid};
#[cfg(feature = "use-openssl")]
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "default", feature = "use-rustls"),
not(feature = "use-openssl")
))]
use rustls::{
pki_types::ServerName,
pki_types::{Der, TrustAnchor},
ClientConfig, ClientConnection, RootCertStore, StreamOwned,
ClientConfig, ClientConnection, OwnedTrustAnchor, RootCertStore, ServerName, StreamOwned,
};
#[cfg(any(feature = "default", feature = "proxy"))]
use crate::socks::{Socks5Stream, TargetAddr, ToTargetAddr};
use crate::stream::ClonableStream;
use stream::ClonableStream;
use crate::api::ElectrumApi;
use crate::batch::Batch;
use crate::types::*;
use api::ElectrumApi;
use batch::Batch;
use types::*;
macro_rules! impl_batch_call {
( $self:expr, $data:expr, $call:ident ) => {{
@ -83,7 +76,7 @@ pub trait ToSocketAddrsDomain: ToSocketAddrs {
impl ToSocketAddrsDomain for &str {
fn domain(&self) -> Option<&str> {
self.split(':').next()
self.splitn(2, ':').next()
}
}
@ -290,81 +283,40 @@ impl RawClient<ElectrumSslStream> {
}
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "default", feature = "use-rustls"),
not(feature = "use-openssl")
))]
mod danger {
use crate::raw_client::ServerName;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
use rustls::crypto::CryptoProvider;
use rustls::pki_types::{CertificateDer, UnixTime};
use rustls::DigitallySignedStruct;
use rustls;
use rustls::client::ServerCertVerified;
use rustls::{Certificate, Error, ServerName};
use std::time::SystemTime;
#[derive(Debug)]
pub struct NoCertificateVerification(CryptoProvider);
pub struct NoCertificateVerification {}
impl NoCertificateVerification {
pub fn new(provider: CryptoProvider) -> Self {
Self(provider)
}
}
impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
impl rustls::client::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
_end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: SystemTime,
) -> Result<ServerCertVerified, Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
}
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "default", feature = "use-rustls"),
not(feature = "use-openssl")
))]
/// Transport type used to establish a Rustls TLS encrypted/authenticated connection with the server
pub type ElectrumSslStream = StreamOwned<ClientConnection, TcpStream>;
#[cfg(all(
any(
feature = "default",
feature = "use-rustls",
feature = "use-rustls-ring"
),
any(feature = "default", feature = "use-rustls"),
not(feature = "use-openssl")
))]
impl RawClient<ElectrumSslStream> {
@ -406,53 +358,26 @@ 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 builder = ClientConfig::builder().with_safe_defaults();
let config = if validate_domain {
socket_addr.domain().ok_or(Error::MissingDomain)?;
let store = webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|t| TrustAnchor {
subject: Der::from_slice(t.subject),
subject_public_key_info: Der::from_slice(t.spki),
name_constraints: t.name_constraints.map(Der::from_slice),
})
.collect::<RootCertStore>();
let mut store = RootCertStore::empty();
store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.into_iter().map(|t| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
t.subject,
t.spki,
t.name_constraints,
)
}));
// TODO: cert pinning
builder.with_root_certificates(store).with_no_client_auth()
} else {
builder
.dangerous()
.with_custom_certificate_verifier(std::sync::Arc::new(
#[cfg(all(feature = "use-rustls", not(feature = "use-rustls-ring")))]
danger::NoCertificateVerification::new(rustls::crypto::aws_lc_rs::default_provider()),
#[cfg(feature = "use-rustls-ring")]
danger::NoCertificateVerification::new(rustls::crypto::ring::default_provider()),
danger::NoCertificateVerification {},
))
.with_no_client_auth()
};
@ -460,7 +385,7 @@ impl RawClient<ElectrumSslStream> {
let domain = socket_addr.domain().unwrap_or("NONE").to_string();
let session = ClientConnection::new(
std::sync::Arc::new(config),
ServerName::try_from(domain.clone())
ServerName::try_from(domain.as_str())
.map_err(|_| Error::InvalidDNSNameError(domain.clone()))?,
)
.map_err(Error::CouldNotCreateConnection)?;
@ -499,11 +424,7 @@ impl RawClient<ElectrumProxyStream> {
Ok(stream.into())
}
#[cfg(any(
feature = "use-openssl",
feature = "use-rustls",
feature = "use-rustls-ring"
))]
#[cfg(any(feature = "use-openssl", feature = "use-rustls"))]
/// Creates a new TLS client that connects to `target_addr` using `proxy_addr` as a socks proxy
/// server. The DNS resolution of `target_addr`, if required, is done through the proxy. This
/// allows to specify, for instance, `.onion` addresses.
@ -562,10 +483,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);
}
@ -636,7 +558,7 @@ impl<S: Read + Write> RawClient<S> {
// No id, that's probably a notification.
let mut resp = resp;
if let Some(method) = resp["method"].take().as_str() {
if let Some(ref method) = resp["method"].take().as_str() {
self.handle_notification(method, resp["params"].take())?;
} else {
warn!("Unexpected response: {:?}", resp);
@ -753,7 +675,7 @@ impl<S: Read + Write> RawClient<S> {
) -> Result<serde_json::Value, Error> {
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
method_name,
&method_name,
params,
);
let result = self.call(req)?;
@ -784,23 +706,22 @@ 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(
self.last_id.fetch_add(1, Ordering::SeqCst),
method,
&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 +740,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 +749,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);
@ -836,7 +757,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
};
}
Ok(answers.into_values().collect())
Ok(answers.into_iter().map(|(_, r)| r).collect())
}
fn block_headers_subscribe_raw(&self) -> Result<RawHeaderNotification, Error> {
@ -1125,49 +1046,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(
self.last_id.fetch_add(1, Ordering::SeqCst),
"blockchain.transaction.id_from_pos",
params,
);
let result = self.call(req)?;
Ok(serde_json::from_value(result)?)
}
fn txid_from_pos_with_merkle(
&self,
height: usize,
tx_pos: usize,
) -> Result<TxidFromPosRes, Error> {
let params = vec![
Param::Usize(height),
Param::Usize(tx_pos),
Param::Bool(true),
];
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
"blockchain.transaction.id_from_pos",
params,
);
let result = self.call(req)?;
Ok(serde_json::from_value(result)?)
}
fn server_features(&self) -> Result<ServerFeaturesRes, Error> {
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
@ -1203,7 +1081,7 @@ mod test {
use crate::utils;
use super::RawClient;
use crate::api::ElectrumApi;
use api::ElectrumApi;
fn get_test_server() -> String {
std::env::var("TEST_ELECTRUM_SERVER").unwrap_or("electrum.blockstream.info:50001".into())
@ -1224,32 +1102,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,131 +1340,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;
let client = RawClient::new(get_test_server(), None).unwrap();
let txid =
Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d")
.unwrap();
let resp = client.txid_from_pos(630000, 68).unwrap();
assert_eq!(resp, txid);
}
#[test]
fn test_txid_from_pos_with_merkle() {
use bitcoin::Txid;
let client = RawClient::new(get_test_server(), None).unwrap();
let txid =
Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d")
.unwrap();
let resp = client.txid_from_pos_with_merkle(630000, 68).unwrap();
assert_eq!(resp.tx_hash, txid);
assert_eq!(
resp.merkle[0],
[
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
]
);
}
#[test]
fn test_ping() {
let client = RawClient::new(get_test_server(), None).unwrap();
@ -1646,7 +1379,7 @@ mod test {
#[test]
fn test_raw_call() {
use crate::types::Param;
use types::Param;
let client = RawClient::new(get_test_server(), None).unwrap();

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,
@ -104,7 +109,7 @@ impl Socks4Stream {
let _ = packet.write_u32::<BigEndian>(Ipv4Addr::new(0, 0, 0, 1).into());
let _ = packet.write_all(userid.as_bytes());
let _ = packet.write_u8(0);
packet.extend(host.as_bytes());
let _ = packet.extend(host.as_bytes());
let _ = packet.write_u8(0);
}
}
@ -112,7 +117,10 @@ impl Socks4Stream {
socket.write_all(&packet)?;
let proxy_addr = read_response(&mut socket)?;
Ok(Socks4Stream { socket, proxy_addr })
Ok(Socks4Stream {
socket: socket,
proxy_addr: proxy_addr,
})
}
/// Returns the proxy-side address of the connection between the proxy and
@ -143,7 +151,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 +167,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 {
@ -87,7 +110,7 @@ fn write_addr(mut packet: &mut [u8], target: &TargetAddr) -> io::Result<usize> {
}
TargetAddr::Domain(ref domain, port) => {
packet.write_u8(3).unwrap();
if domain.len() > u8::MAX as usize {
if domain.len() > u8::max_value() as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"domain name too long",
@ -112,7 +135,7 @@ enum Authentication<'a> {
None,
}
impl Authentication<'_> {
impl<'a> Authentication<'a> {
fn id(&self) -> u8 {
match *self {
Authentication::Password { .. } => 2,
@ -121,7 +144,11 @@ impl Authentication<'_> {
}
fn is_no_auth(&self) -> bool {
matches!(*self, Authentication::None)
if let Authentication::None = *self {
true
} else {
false
}
}
}
@ -204,11 +231,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 {
@ -227,7 +257,10 @@ impl Socks5Stream {
let proxy_addr = read_response(&mut socket)?;
Ok(Socks5Stream { socket, proxy_addr })
Ok(Socks5Stream {
socket: socket,
proxy_addr: proxy_addr,
})
}
fn password_authentication(
@ -235,13 +268,13 @@ impl Socks5Stream {
username: &str,
password: &str,
) -> io::Result<()> {
if username.is_empty() || username.len() > 255 {
if username.len() < 1 || username.len() > 255 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid username",
));
};
if password.is_empty() || password.len() > 255 {
if password.len() < 1 || password.len() > 255 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid password",
@ -303,7 +336,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 +352,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)
}
@ -441,7 +474,10 @@ impl Socks5Datagram {
let socket = UdpSocket::bind(addr)?;
socket.connect(&stream.proxy_addr)?;
Ok(Socks5Datagram { socket, stream })
Ok(Socks5Datagram {
socket: socket,
stream: stream,
})
}
/// Like `UdpSocket::send_to`.
@ -490,7 +526,11 @@ impl Socks5Datagram {
let addr = read_addr(&mut header)?;
unsafe {
ptr::copy(buf.as_ptr(), buf.as_mut_ptr().add(header.len()), overflow);
ptr::copy(
buf.as_ptr(),
buf.as_mut_ptr().offset(header.len() as isize),
overflow,
);
}
buf[..header.len()].copy_from_slice(header);

View File

@ -87,7 +87,7 @@ impl From<[u8; 32]> for Hex32Bytes {
}
impl Hex32Bytes {
pub(crate) fn to_hex(self) -> String {
pub(crate) fn to_hex(&self) -> String {
self.0.to_lower_hex_string()
}
}
@ -240,17 +240,6 @@ pub struct GetMerkleRes {
pub merkle: Vec<[u8; 32]>,
}
/// Response to a [`txid_from_pos_with_merkle`](../client/struct.Client.html#method.txid_from_pos_with_merkle)
/// request.
#[derive(Clone, Debug, Deserialize)]
pub struct TxidFromPosRes {
/// Txid of the transaction.
pub tx_hash: Txid,
/// The merkle path of the transaction.
#[serde(deserialize_with = "from_hex_array")]
pub merkle: Vec<[u8; 32]>,
}
/// Notification of a new block header
#[derive(Clone, Debug, Deserialize)]
pub struct HeaderNotification {
@ -326,7 +315,8 @@ pub enum Error {
CouldntLockReader,
/// Broken IPC communication channel: the other thread probably has exited
Mpsc,
#[cfg(any(feature = "use-rustls", feature = "use-rustls-ring"))]
#[cfg(feature = "use-rustls")]
/// Could not create a rustls client connection
CouldNotCreateConnection(rustls::Error),
@ -350,10 +340,7 @@ impl Display for Error {
Error::SslHandshakeError(e) => Display::fmt(e, f),
#[cfg(feature = "use-openssl")]
Error::InvalidSslMethod(e) => Display::fmt(e, f),
#[cfg(any(
feature = "use-rustls",
feature = "use-rustls-ring",
))]
#[cfg(feature = "use-rustls")]
Error::CouldNotCreateConnection(e) => Display::fmt(e, f),
Error::Message(e) => f.write_str(e),

View File

@ -1,10 +1,10 @@
//! Utilities helping to handle Electrum-related data.
use crate::types::GetMerkleRes;
use bitcoin::hash_types::TxMerkleNode;
use bitcoin::hashes::sha256d::Hash as Sha256d;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::Hash;
use bitcoin::Txid;
use types::GetMerkleRes;
/// Verifies a Merkle inclusion proof as retrieved via [`transaction_get_merkle`] for a transaction with the
/// given `txid` and `merkle_root` as included in the [`BlockHeader`].
@ -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,
@ -21,21 +21,21 @@ pub fn validate_merkle_proof(
) -> bool {
let mut index = merkle_res.pos;
let mut cur = txid.to_raw_hash();
for mut bytes in merkle_res.merkle.iter().cloned() {
bytes.reverse();
let next_hash = Sha256d::from_byte_array(bytes);
for bytes in &merkle_res.merkle {
let mut reversed = [0u8; 32];
reversed.copy_from_slice(bytes);
reversed.reverse();
// unwrap() safety: `reversed` has len 32 so `from_slice` can never fail.
let next_hash = Sha256d::from_slice(&reversed).unwrap();
cur = Sha256d::from_engine({
let mut engine = Sha256d::engine();
if index % 2 == 0 {
engine.input(cur.as_ref());
engine.input(next_hash.as_ref());
} else {
engine.input(next_hash.as_ref());
engine.input(cur.as_ref());
};
engine
});
let (left, right) = if index % 2 == 0 {
(cur, next_hash)
} else {
(next_hash, cur)
};
let data = [&left[..], &right[..]].concat();
cur = Sha256d::hash(&data);
index /= 2;
}