Compare commits

..

2 Commits

Author SHA1 Message Date
Alekos Filini
84f11fb80e
Bump to v0.14.1
Some checks failed
CI / Test (push) Has been cancelled
2023-05-14 12:59:00 +02:00
Riccardo Casatta
16e1a03414
Retry client should return AlreadySubscribed error
Users may leverage the Error to avoid remembering client-side the subscription
status. eg. Always subscribing and calling script_pop in case AlreadySubscribed
is returned.
2023-05-14 12:58:39 +02:00
17 changed files with 258 additions and 1196 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

@ -1,66 +0,0 @@
# Changelog
All notable changes to this project can be found here and in each release's git tag and can be viewed with `git tag -ln100 "v*"`.
Contributors do not need to change this file but do need to add changelog details in their PR descriptions. The person making the next release will collect changelog details from included PRs and edit this file prior to each release.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [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
- Remove webpki and bump webpki-roots to v0.25 #117
- Upgrade rust-bitcoin to v0.31.0 #121
- Add utility to validate GetMerkleRes #122
- Enforce timeout on initial socks5 proxy connection #125
## [0.18.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

View File

@ -1,16 +1,14 @@
[package]
name = "electrum-client"
version = "0.24.1"
version = "0.14.1"
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,14 +18,15 @@ path = "src/lib.rs"
[dependencies]
log = "^0.4"
bitcoin = { version = "0.32", features = ["serde"] }
bitcoin = { version = "^0.29", 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 }
webpki-roots = { version = "0.25", optional = true }
rustls = { version = "0.20", optional = true, features = ["dangerous_configuration"] }
webpki = { version = "0.22", optional = true }
webpki-roots = { version = "0.22", optional = true }
byteorder = { version = "1.0", optional = true }
@ -42,6 +41,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", "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,190 +2,17 @@
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 bitcoin::{BlockHeader, 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 {
/// Gets the block header for height `height`.
fn block_header(&self, height: usize) -> Result<block::Header, Error> {
fn block_header(&self, height: usize) -> Result<BlockHeader, Error> {
Ok(deserialize(&self.block_header_raw(height)?)?)
}
@ -224,7 +51,7 @@ pub trait ElectrumApi {
/// 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<I>(&self, heights: I) -> Result<Vec<block::Header>, Error>
fn batch_block_header<I>(&self, heights: I) -> Result<Vec<BlockHeader>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<u32>,
@ -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

@ -2,9 +2,12 @@
//!
//! This module contains definitions and helper functions used when making batch calls.
use std::borrow::Borrow;
use bitcoin::hashes::hex::ToHex;
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,17 +19,11 @@ 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>,
}
impl Batch {
/// Add a raw request to the batch queue
pub fn raw(&mut self, method: String, params: Vec<Param>) {
self.calls.push((method, params));
}
/// Add one `blockchain.scripthash.listunspent` request to the batch queue
pub fn script_list_unspent(&mut self, script: &Script) {
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
@ -57,22 +54,11 @@ impl Batch {
/// Add one `blockchain.transaction.get` request to the batch queue
pub fn transaction_get(&mut self, tx_hash: &Txid) {
let params = vec![Param::String(format!("{:x}", tx_hash))];
let params = vec![Param::String(tx_hash.to_hex())];
self.calls
.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 +74,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 +105,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

@ -1,11 +1,6 @@
use crate::Error;
use std::time::Duration;
/// Configuration for an electrum client
///
/// Refer to [`Client::from_config`] and [`ClientType::from_config`].
///
/// [`Client::from_config`]: crate::Client::from_config
/// [`ClientType::from_config`]: crate::ClientType::from_config
#[derive(Debug, Clone)]
pub struct Config {
/// Proxy socks5 configuration, default None
@ -55,8 +50,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
}
@ -103,35 +98,19 @@ impl Socks5Config {
}
impl Config {
/// Get the configuration for `socks5`
///
/// Set this with [`ConfigBuilder::socks5`]
pub fn socks5(&self) -> &Option<Socks5Config> {
&self.socks5
}
/// Get the configuration for `retry`
///
/// Set this with [`ConfigBuilder::retry`]
pub fn retry(&self) -> u8 {
self.retry
}
/// Get the configuration for `timeout`
///
/// Set this with [`ConfigBuilder::timeout`]
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
/// Get the configuration for `validate_domain`
///
/// Set this with [`ConfigBuilder::validate_domain`]
pub fn validate_domain(&self) -> bool {
self.validate_domain
}
/// Convenience method for calling [`ConfigBuilder::new`]
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}

View File

@ -25,22 +25,16 @@ 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;
#[cfg(any(feature = "use-rustls", feature = "default"))]
extern crate webpki_roots;
#[cfg(any(feature = "default", feature = "proxy"))]
@ -59,8 +53,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;
@ -69,14 +62,12 @@ mod config;
pub mod raw_client;
mod stream;
mod types;
pub mod utils;
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};
@ -16,34 +16,27 @@ use std::time::Duration;
use log::{debug, error, info, trace, warn};
use bitcoin::consensus::encode::deserialize;
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::hashes::hex::{FromHex, ToHex};
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_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.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> {
@ -1053,7 +974,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
}
fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error> {
let params = vec![Param::String(format!("{:x}", txid))];
let params = vec![Param::String(txid.to_hex())];
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
"blockchain.transaction.get",
@ -1102,7 +1023,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
}
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
let params = vec![Param::String(raw_tx.to_lower_hex_string())];
let params = vec![Param::String(raw_tx.to_hex())];
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
"blockchain.transaction.broadcast",
@ -1114,7 +1035,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
}
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
let params = vec![Param::String(format!("{:x}", txid)), Param::Usize(height)];
let params = vec![Param::String(txid.to_hex()), Param::Usize(height)];
let req = Request::new_id(
self.last_id.fetch_add(1, Ordering::SeqCst),
"blockchain.transaction.get_merkle",
@ -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),
@ -1198,12 +1076,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
#[cfg(test)]
mod test {
use std::str::FromStr;
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 +1098,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]
@ -1265,7 +1119,7 @@ mod test {
let client = RawClient::new(get_test_server(), None).unwrap();
let resp = client.block_header(0).unwrap();
assert_eq!(resp.version, bitcoin::block::Version::ONE);
assert_eq!(resp.version, 0x01);
assert_eq!(resp.time, 1231006505);
assert_eq!(resp.nonce, 0x7c2bac1d);
}
@ -1306,9 +1160,7 @@ mod test {
// Realistically nobody will ever spend from this address, so we can expect the balance to
// increase over time
let addr = bitcoin::Address::from_str("1CounterpartyXXXXXXXXXXXXXXXUWLpVr")
.unwrap()
.assume_checked();
let addr = bitcoin::Address::from_str("1CounterpartyXXXXXXXXXXXXXXXUWLpVr").unwrap();
let resp = client.script_get_balance(&addr.script_pubkey()).unwrap();
assert!(resp.confirmed >= 213091301265);
}
@ -1317,45 +1169,43 @@ mod test {
fn test_script_get_history() {
use std::str::FromStr;
use bitcoin::hashes::hex::FromHex;
use bitcoin::Txid;
let client = RawClient::new(get_test_server(), None).unwrap();
// Mt.Gox hack address
let addr = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF")
.unwrap()
.assume_checked();
let addr = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF").unwrap();
let resp = client.script_get_history(&addr.script_pubkey()).unwrap();
assert!(resp.len() >= 328);
assert_eq!(
resp[0].tx_hash,
Txid::from_str("e67a0550848b7932d7796aeea16ab0e48a5cfe81c4e8cca2c5b03e0416850114")
Txid::from_hex("e67a0550848b7932d7796aeea16ab0e48a5cfe81c4e8cca2c5b03e0416850114")
.unwrap()
);
}
#[test]
fn test_script_list_unspent() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::Txid;
use std::str::FromStr;
let client = RawClient::new(get_test_server(), None).unwrap();
// Peter todd's sha256 bounty address https://bitcointalk.org/index.php?topic=293382.0
let addr = bitcoin::Address::from_str("35Snmmy3uhaer2gTboc81ayCip4m9DT4ko")
.unwrap()
.assume_checked();
// Mt.Gox hack address
let addr = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF").unwrap();
let resp = client.script_list_unspent(&addr.script_pubkey()).unwrap();
assert!(resp.len() >= 9);
let txid = "397f12ee15f8a3d2ab25c0f6bb7d3c64d2038ca056af10dd8251b98ae0f076b0";
let txid = Txid::from_str(txid).unwrap();
assert!(resp.len() >= 329);
let txid = "e67a0550848b7932d7796aeea16ab0e48a5cfe81c4e8cca2c5b03e0416850114";
let txid = Txid::from_hex(txid).unwrap();
let txs: Vec<_> = resp.iter().filter(|e| e.tx_hash == txid).collect();
assert_eq!(txs.len(), 1);
assert_eq!(txs[0].value, 10000000);
assert_eq!(txs[0].height, 257674);
assert_eq!(txs[0].tx_pos, 1);
assert_eq!(txs[0].value, 7995600000000);
assert_eq!(txs[0].height, 111194);
assert_eq!(txs[0].tx_pos, 0);
}
#[test]
@ -1364,17 +1214,14 @@ mod test {
let client = RawClient::new(get_test_server(), None).unwrap();
// Peter todd's sha256 bounty address https://bitcointalk.org/index.php?topic=293382.0
let script_1 = bitcoin::Address::from_str("35Snmmy3uhaer2gTboc81ayCip4m9DT4ko")
// Mt.Gox hack address
let script_1 = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF")
.unwrap()
.assume_checked()
.script_pubkey();
let resp = client
.batch_script_list_unspent(vec![script_1.as_script()])
.unwrap();
let resp = client.batch_script_list_unspent(vec![&script_1]).unwrap();
assert_eq!(resp.len(), 1);
assert!(resp[0].len() >= 9);
assert!(resp[0].len() >= 329);
}
#[test]
@ -1389,29 +1236,31 @@ mod test {
#[test]
fn test_transaction_get() {
use bitcoin::{transaction, Txid};
use bitcoin::hashes::hex::FromHex;
use bitcoin::Txid;
let client = RawClient::new(get_test_server(), None).unwrap();
let resp = client
.transaction_get(
&Txid::from_str("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
&Txid::from_hex("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
.unwrap(),
)
.unwrap();
assert_eq!(resp.version, transaction::Version::ONE);
assert_eq!(resp.lock_time.to_consensus_u32(), 0);
assert_eq!(resp.version, 1);
assert_eq!(resp.lock_time.to_u32(), 0);
}
#[test]
fn test_transaction_get_raw() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::Txid;
let client = RawClient::new(get_test_server(), None).unwrap();
let resp = client
.transaction_get_raw(
&Txid::from_str("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
&Txid::from_hex("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
.unwrap(),
)
.unwrap();
@ -1439,170 +1288,26 @@ mod test {
#[test]
fn test_transaction_get_merkle() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::Txid;
let client = RawClient::new(get_test_server(), None).unwrap();
let txid =
Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d")
.unwrap();
let resp = client.transaction_get_merkle(&txid, 630000).unwrap();
let resp = client
.transaction_get_merkle(
&Txid::from_hex("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
.unwrap(),
630000,
)
.unwrap();
assert_eq!(resp.block_height, 630000);
assert_eq!(resp.pos, 68);
assert_eq!(resp.pos, 0);
assert_eq!(resp.merkle.len(), 12);
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
]
);
// Check we can verify the merkle proof validity, but fail if we supply wrong data.
let block_header = client.block_header(resp.block_height).unwrap();
assert!(utils::validate_merkle_proof(
&txid,
&block_header.merkle_root,
&resp
));
let mut fail_resp = resp.clone();
fail_resp.pos = 13;
assert!(!utils::validate_merkle_proof(
&txid,
&block_header.merkle_root,
&fail_resp
));
let fail_block_header = client.block_header(resp.block_height + 1).unwrap();
assert!(!utils::validate_merkle_proof(
&txid,
&fail_block_header.merkle_root,
&resp
));
}
#[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
30, 10, 161, 245, 132, 125, 136, 198, 186, 138, 107, 216, 92, 22, 145, 81, 130,
126, 200, 65, 121, 158, 105, 111, 38, 151, 38, 147, 144, 224, 5, 218
]
);
}
@ -1628,9 +1333,7 @@ mod test {
let client = RawClient::new(get_test_server(), None).unwrap();
// Mt.Gox hack address
let addr = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF")
.unwrap()
.assume_checked();
let addr = bitcoin::Address::from_str("1FeexV6bAHb8ybZjqQMjJrcCrHGW9sb6uF").unwrap();
// Just make sure that the call returns Ok(something)
client.script_subscribe(&addr.script_pubkey()).unwrap();
@ -1646,7 +1349,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
}
}
}
@ -177,9 +204,6 @@ impl Socks5Stream {
TcpStream::connect(proxy)?
};
socket.set_read_timeout(timeout)?;
socket.set_write_timeout(timeout)?;
let target = target.to_target_addr()?;
let packet_len = if auth.is_no_auth() { 3 } else { 4 };
@ -204,11 +228,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 +254,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 +265,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 +333,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 +349,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 +471,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 +523,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

@ -9,15 +9,15 @@ use std::sync::Arc;
use bitcoin::blockdata::block;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{Script, Txid};
use serde::{de, Deserialize, Serialize};
static JSONRPC_2_0: &str = "2.0";
pub(crate) type Call = (String, Vec<Param>);
pub type Call = (String, Vec<Param>);
#[derive(Serialize, Clone)]
#[serde(untagged)]
@ -86,12 +86,6 @@ impl From<[u8; 32]> for Hex32Bytes {
}
}
impl Hex32Bytes {
pub(crate) fn to_hex(self) -> String {
self.0.to_lower_hex_string()
}
}
/// Format used by the Electrum server to identify an address. The reverse sha256 hash of the
/// scriptPubKey. Documented [here](https://electrumx.readthedocs.io/en/latest/protocol-basics.html#script-hashes).
pub type ScriptHash = Hex32Bytes;
@ -108,7 +102,7 @@ pub trait ToElectrumScriptHash {
impl ToElectrumScriptHash for Script {
fn to_electrum_scripthash(&self) -> ScriptHash {
let mut result = sha256::Hash::hash(self.as_bytes()).to_byte_array();
let mut result = sha256::Hash::hash(self.as_bytes()).into_inner();
result.reverse();
result.into()
@ -128,7 +122,7 @@ fn to_hex<S>(bytes: &[u8], serializer: S) -> std::result::Result<S::Ok, S::Error
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&bytes.to_lower_hex_string())
serializer.serialize_str(&bytes.to_hex())
}
fn from_hex_array<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
@ -151,7 +145,7 @@ where
Ok(answer)
}
fn from_hex_header<'de, D>(deserializer: D) -> Result<block::Header, D::Error>
fn from_hex_header<'de, D>(deserializer: D) -> Result<block::BlockHeader, D::Error>
where
D: de::Deserializer<'de>,
{
@ -160,7 +154,7 @@ where
}
/// Response to a [`script_get_history`](../client/struct.Client.html#method.script_get_history) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct GetHistoryRes {
/// Confirmation height of the transaction. 0 if unconfirmed, -1 if unconfirmed while some of
/// its inputs are unconfirmed too.
@ -172,7 +166,7 @@ pub struct GetHistoryRes {
}
/// Response to a [`script_list_unspent`](../client/struct.Client.html#method.script_list_unspent) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct ListUnspentRes {
/// Confirmation height of the transaction that created this output.
pub height: usize,
@ -185,7 +179,7 @@ pub struct ListUnspentRes {
}
/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct ServerFeaturesRes {
/// Server version reported.
pub server_version: String,
@ -203,7 +197,7 @@ pub struct ServerFeaturesRes {
}
/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct GetHeadersRes {
/// Maximum number of headers returned in a single response.
pub max: usize,
@ -214,11 +208,11 @@ pub struct GetHeadersRes {
pub raw_headers: Vec<u8>,
/// Array of block headers.
#[serde(skip)]
pub headers: Vec<block::Header>,
pub headers: Vec<block::BlockHeader>,
}
/// Response to a [`script_get_balance`](../client/struct.Client.html#method.script_get_balance) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct GetBalanceRes {
/// Confirmed balance in Satoshis for the address.
pub confirmed: u64,
@ -229,7 +223,7 @@ pub struct GetBalanceRes {
}
/// Response to a [`transaction_get_merkle`](../client/struct.Client.html#method.transaction_get_merkle) request.
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct GetMerkleRes {
/// Height of the block that confirmed the transaction
pub block_height: usize,
@ -240,29 +234,18 @@ 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)]
#[derive(Debug, Deserialize)]
pub struct HeaderNotification {
/// New block height.
pub height: usize,
/// Newly added header.
#[serde(rename = "hex", deserialize_with = "from_hex_header")]
pub header: block::Header,
pub header: block::BlockHeader,
}
/// Notification of a new block header with the header encoded as raw bytes
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct RawHeaderNotification {
/// New block height.
pub height: usize,
@ -283,7 +266,7 @@ impl TryFrom<RawHeaderNotification> for HeaderNotification {
}
/// Notification of the new status of a script
#[derive(Clone, Debug, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct ScriptNotification {
/// Address that generated this notification.
pub scripthash: ScriptHash,
@ -298,8 +281,8 @@ pub enum Error {
IOError(std::io::Error),
/// Wraps `serde_json::error::Error`
JSON(serde_json::error::Error),
/// Wraps `bitcoin::hex::HexToBytesError`
Hex(bitcoin::hex::HexToBytesError),
/// Wraps `bitcoin::hashes::hex::Error`
Hex(bitcoin::hashes::hex::Error),
/// Error returned by the Electrum server
Protocol(serde_json::Value),
/// Error during the deserialization of a Bitcoin data structure
@ -326,7 +309,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 +334,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),
@ -394,7 +375,7 @@ macro_rules! impl_error {
impl_error!(std::io::Error, IOError);
impl_error!(serde_json::Error, JSON);
impl_error!(bitcoin::hex::HexToBytesError, Hex);
impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::consensus::encode::Error, Bitcoin);
impl<T> From<std::sync::PoisonError<T>> for Error {

View File

@ -1,43 +0,0 @@
//! 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::Txid;
/// 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`].
///
/// Returns `true` if the transaction is included in the corresponding block, and `false`
/// otherwise.
///
/// [`transaction_get_merkle`]: crate::ElectrumApi::transaction_get_merkle
/// [`BlockHeader`]: bitcoin::block::Header
pub fn validate_merkle_proof(
txid: &Txid,
merkle_root: &TxMerkleNode,
merkle_res: &GetMerkleRes,
) -> 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);
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
});
index /= 2;
}
cur == merkle_root.to_raw_hash()
}