Compare commits
2 Commits
master
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fae704b7c | ||
|
|
89af226aa6 |
56
.github/workflows/cont_integration.yml
vendored
56
.github/workflows/cont_integration.yml
vendored
@ -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
|
||||
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@ -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
|
||||
18
Cargo.toml
18
Cargo.toml
@ -1,16 +1,14 @@
|
||||
[package]
|
||||
name = "electrum-client"
|
||||
version = "0.24.1"
|
||||
version = "0.12.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"]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -1 +0,0 @@
|
||||
msrv="1.75.0"
|
||||
@ -6,7 +6,7 @@ fn main() {
|
||||
// NOTE: This assumes Tor is running localy, with an unauthenticated Socks5 listening at
|
||||
// localhost:9050
|
||||
let proxy = Socks5Config::new("127.0.0.1:9050");
|
||||
let config = ConfigBuilder::new().socks5(Some(proxy)).build();
|
||||
let config = ConfigBuilder::new().socks5(Some(proxy)).unwrap().build();
|
||||
|
||||
let client = Client::from_config("tcp://explorernuoc63nb.onion:110", config.clone()).unwrap();
|
||||
let res = client.server_features();
|
||||
|
||||
34
justfile
34
justfile
@ -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
|
||||
|
||||
462
src/api.rs
462
src/api.rs
@ -1,191 +1,17 @@
|
||||
//! Electrum APIs
|
||||
|
||||
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)?)?)
|
||||
}
|
||||
|
||||
@ -212,8 +38,7 @@ pub trait ElectrumApi {
|
||||
/// Takes a list of `txids` and returns a list of transactions.
|
||||
fn batch_transaction_get<'t, I>(&self, txids: I) -> Result<Vec<Transaction>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'t Txid>,
|
||||
I: IntoIterator<Item = &'t Txid> + Clone,
|
||||
{
|
||||
self.batch_transaction_get_raw(txids)?
|
||||
.iter()
|
||||
@ -224,10 +49,9 @@ 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>,
|
||||
I: IntoIterator<Item = u32> + Clone,
|
||||
{
|
||||
self.batch_block_header_raw(heights)?
|
||||
.iter()
|
||||
@ -282,16 +106,6 @@ pub trait ElectrumApi {
|
||||
/// already subscribed to the script.
|
||||
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error>;
|
||||
|
||||
/// Batch version of [`script_subscribe`](#method.script_subscribe).
|
||||
///
|
||||
/// Takes a list of scripts and returns a list of script status responses.
|
||||
///
|
||||
/// Note you should pass a reference to a collection because otherwise an expensive clone is made
|
||||
fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>;
|
||||
|
||||
/// Subscribes to notifications for activity on a specific *scriptPubKey*.
|
||||
///
|
||||
/// Returns a `bool` with the server response when successful.
|
||||
@ -311,8 +125,7 @@ pub trait ElectrumApi {
|
||||
/// Takes a list of scripts and returns a list of balance responses.
|
||||
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>;
|
||||
I: IntoIterator<Item = &'s Script> + Clone;
|
||||
|
||||
/// Returns the history for a *scriptPubKey*
|
||||
fn script_get_history(&self, script: &Script) -> Result<Vec<GetHistoryRes>, Error>;
|
||||
@ -322,8 +135,7 @@ pub trait ElectrumApi {
|
||||
/// Takes a list of scripts and returns a list of history responses.
|
||||
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>;
|
||||
I: IntoIterator<Item = &'s Script> + Clone;
|
||||
|
||||
/// Returns the list of unspent outputs for a *scriptPubKey*
|
||||
fn script_list_unspent(&self, script: &Script) -> Result<Vec<ListUnspentRes>, Error>;
|
||||
@ -336,8 +148,7 @@ pub trait ElectrumApi {
|
||||
scripts: I,
|
||||
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>;
|
||||
I: IntoIterator<Item = &'s Script> + Clone;
|
||||
|
||||
/// Gets the raw bytes of a transaction with `txid`. Returns an error if not found.
|
||||
fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error>;
|
||||
@ -347,16 +158,14 @@ pub trait ElectrumApi {
|
||||
/// Takes a list of `txids` and returns a list of transactions raw bytes.
|
||||
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'t Txid>;
|
||||
I: IntoIterator<Item = &'t Txid> + Clone;
|
||||
|
||||
/// Batch version of [`block_header_raw`](#method.block_header_raw).
|
||||
///
|
||||
/// Takes a list of `heights` of blocks and returns a list of block header raw bytes.
|
||||
fn batch_block_header_raw<I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<u32>;
|
||||
I: IntoIterator<Item = u32> + Clone;
|
||||
|
||||
/// Batch version of [`estimate_fee`](#method.estimate_fee).
|
||||
///
|
||||
@ -364,8 +173,7 @@ pub trait ElectrumApi {
|
||||
/// **Satoshis per kilobyte** to confirm a transaction in the given number of blocks.
|
||||
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<usize>;
|
||||
I: IntoIterator<Item = usize> + Clone;
|
||||
|
||||
/// Broadcasts the raw bytes of a transaction to the network.
|
||||
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error>;
|
||||
@ -373,28 +181,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 +192,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>>();
|
||||
}
|
||||
}
|
||||
|
||||
37
src/batch.rs
37
src/batch.rs
@ -2,9 +2,10 @@
|
||||
//!
|
||||
//! This module contains definitions and helper functions used when making batch calls.
|
||||
|
||||
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 +17,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())];
|
||||
@ -48,31 +43,13 @@ impl Batch {
|
||||
.push((String::from("blockchain.scripthash.get_balance"), params));
|
||||
}
|
||||
|
||||
/// Add one `blockchain.scripthash.listunspent` request to the batch queue
|
||||
pub fn script_subscribe(&mut self, script: &Script) {
|
||||
let params = vec![Param::String(script.to_electrum_scripthash().to_hex())];
|
||||
self.calls
|
||||
.push((String::from("blockchain.scripthash.subscribe"), params));
|
||||
}
|
||||
|
||||
/// 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 +65,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 +96,9 @@ impl<'a> std::iter::Iterator for BatchIter<'a> {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for Batch {
|
||||
fn default() -> Self {
|
||||
Batch { calls: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
//! Electrum Client
|
||||
|
||||
use std::{borrow::Borrow, sync::RwLock};
|
||||
use std::sync::RwLock;
|
||||
|
||||
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
|
||||
@ -49,7 +49,7 @@ macro_rules! impl_inner_call {
|
||||
drop(read_client);
|
||||
match res {
|
||||
Ok(val) => return Ok(val),
|
||||
Err(Error::Protocol(_) | Error::AlreadySubscribed(_)) => {
|
||||
Err(Error::Protocol(_)) => {
|
||||
return res;
|
||||
},
|
||||
Err(e) => {
|
||||
@ -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())
|
||||
}
|
||||
@ -221,15 +222,6 @@ impl ElectrumApi for Client {
|
||||
impl_inner_call!(self, script_subscribe, script)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
impl_inner_call!(self, batch_script_subscribe, scripts.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error> {
|
||||
impl_inner_call!(self, script_unsubscribe, script)
|
||||
@ -248,8 +240,7 @@ impl ElectrumApi for Client {
|
||||
#[inline]
|
||||
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_script_get_balance, scripts.clone())
|
||||
}
|
||||
@ -262,8 +253,7 @@ impl ElectrumApi for Client {
|
||||
#[inline]
|
||||
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_script_get_history, scripts.clone())
|
||||
}
|
||||
@ -279,8 +269,7 @@ impl ElectrumApi for Client {
|
||||
scripts: I,
|
||||
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_script_list_unspent, scripts.clone())
|
||||
}
|
||||
@ -293,8 +282,7 @@ impl ElectrumApi for Client {
|
||||
#[inline]
|
||||
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'t Txid>,
|
||||
I: IntoIterator<Item = &'t Txid> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_transaction_get_raw, txids.clone())
|
||||
}
|
||||
@ -302,8 +290,7 @@ impl ElectrumApi for Client {
|
||||
#[inline]
|
||||
fn batch_block_header_raw<'s, I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<u32>,
|
||||
I: IntoIterator<Item = u32> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_block_header_raw, heights.clone())
|
||||
}
|
||||
@ -311,8 +298,7 @@ impl ElectrumApi for Client {
|
||||
#[inline]
|
||||
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<usize>,
|
||||
I: IntoIterator<Item = usize> + Clone,
|
||||
{
|
||||
impl_inner_call!(self, batch_estimate_fee, numbers.clone())
|
||||
}
|
||||
@ -327,36 +313,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 +338,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 +347,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 +393,7 @@ mod tests {
|
||||
sender.send(()).unwrap();
|
||||
|
||||
for _stream in listener.incoming() {
|
||||
std::thread::sleep(Duration::from_secs(60))
|
||||
loop {}
|
||||
}
|
||||
});
|
||||
|
||||
@ -449,7 +405,8 @@ mod tests {
|
||||
let client = Client::from_config(
|
||||
&endpoint,
|
||||
crate::config::ConfigBuilder::new()
|
||||
.timeout(Some(Duration::from_secs(5)))
|
||||
.timeout(Some(5))
|
||||
.unwrap()
|
||||
.build(),
|
||||
);
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
@ -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
|
||||
@ -49,15 +44,15 @@ impl ConfigBuilder {
|
||||
|
||||
/// Set the socks5 config if Some, it accept an `Option` because it's easier for the caller to use
|
||||
/// in a method chain
|
||||
pub fn socks5(mut self, socks5_config: Option<Socks5Config>) -> Self {
|
||||
pub fn socks5(mut self, socks5_config: Option<Socks5Config>) -> Result<Self, Error> {
|
||||
self.config.socks5 = socks5_config;
|
||||
self
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets the timeout
|
||||
pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
|
||||
self.config.timeout = timeout;
|
||||
self
|
||||
pub fn timeout(mut self, timeout: Option<u8>) -> Result<Self, Error> {
|
||||
self.config.timeout = timeout.map(|t| Duration::from_secs(t as u64));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets the retry attempts number
|
||||
@ -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()
|
||||
}
|
||||
|
||||
21
src/lib.rs
21
src/lib.rs
@ -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};
|
||||
|
||||
@ -2,8 +2,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,48 +15,33 @@ 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 ) => {{
|
||||
impl_batch_call!($self, $data, $call, )
|
||||
}};
|
||||
|
||||
( $self:expr, $data:expr, $call:ident, apply_deref ) => {{
|
||||
impl_batch_call!($self, $data, $call, *)
|
||||
}};
|
||||
|
||||
( $self:expr, $data:expr, $call:ident, $($apply_deref:tt)? ) => {{
|
||||
let mut batch = Batch::default();
|
||||
for i in $data {
|
||||
batch.$call($($apply_deref)* i.borrow());
|
||||
batch.$call(i);
|
||||
}
|
||||
|
||||
let resp = $self.batch_call(&batch)?;
|
||||
@ -83,7 +67,7 @@ pub trait ToSocketAddrsDomain: ToSocketAddrs {
|
||||
|
||||
impl ToSocketAddrsDomain for &str {
|
||||
fn domain(&self) -> Option<&str> {
|
||||
self.split(':').next()
|
||||
self.splitn(2, ':').next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,81 +274,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 +349,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 +376,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 +415,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 +474,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 +549,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 +666,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 +697,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 +731,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 +740,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 +748,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> {
|
||||
@ -936,25 +848,6 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
Ok(serde_json::from_value(value)?)
|
||||
}
|
||||
|
||||
fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
{
|
||||
let mut script_notifications = self.script_notifications.lock()?;
|
||||
|
||||
for script in scripts.clone() {
|
||||
let script_hash = script.borrow().to_electrum_scripthash();
|
||||
if script_notifications.contains_key(&script_hash) {
|
||||
return Err(Error::AlreadySubscribed(script_hash));
|
||||
}
|
||||
script_notifications.insert(script_hash, VecDeque::new());
|
||||
}
|
||||
}
|
||||
impl_batch_call!(self, scripts, script_subscribe)
|
||||
}
|
||||
|
||||
fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error> {
|
||||
let script_hash = script.to_electrum_scripthash();
|
||||
let mut script_notifications = self.script_notifications.lock()?;
|
||||
@ -998,8 +891,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
}
|
||||
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_batch_call!(self, scripts, script_get_balance)
|
||||
}
|
||||
@ -1017,8 +909,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
}
|
||||
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_batch_call!(self, scripts, script_get_history)
|
||||
}
|
||||
@ -1046,14 +937,13 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
scripts: I,
|
||||
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
I: IntoIterator<Item = &'s Script> + Clone,
|
||||
{
|
||||
impl_batch_call!(self, scripts, script_list_unspent)
|
||||
}
|
||||
|
||||
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",
|
||||
@ -1070,8 +960,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
|
||||
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'t Txid>,
|
||||
I: IntoIterator<Item = &'t Txid> + Clone,
|
||||
{
|
||||
let txs_string: Result<Vec<String>, Error> = impl_batch_call!(self, txids, transaction_get);
|
||||
txs_string?
|
||||
@ -1082,11 +971,10 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
|
||||
fn batch_block_header_raw<'s, I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<u32>,
|
||||
I: IntoIterator<Item = u32> + Clone,
|
||||
{
|
||||
let headers_string: Result<Vec<String>, Error> =
|
||||
impl_batch_call!(self, heights, block_header, apply_deref);
|
||||
impl_batch_call!(self, heights, block_header);
|
||||
headers_string?
|
||||
.iter()
|
||||
.map(|s| Ok(Vec::<u8>::from_hex(s)?))
|
||||
@ -1095,14 +983,13 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
|
||||
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<usize>,
|
||||
I: IntoIterator<Item = usize> + Clone,
|
||||
{
|
||||
impl_batch_call!(self, numbers, estimate_fee, apply_deref)
|
||||
impl_batch_call!(self, numbers, estimate_fee)
|
||||
}
|
||||
|
||||
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 +1001,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 +1012,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 +1042,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 +1064,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 +1085,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 +1126,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 +1135,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 +1180,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 +1202,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 +1254,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 +1299,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 +1315,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_raw_call() {
|
||||
use crate::types::Param;
|
||||
use types::Param;
|
||||
|
||||
let client = RawClient::new(get_test_server(), None).unwrap();
|
||||
|
||||
|
||||
@ -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>() {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
85
src/types.rs
85
src/types.rs
@ -9,15 +9,15 @@ use std::sync::Arc;
|
||||
|
||||
use bitcoin::blockdata::block;
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
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)]
|
||||
@ -69,8 +69,8 @@ impl<'a> Request<'a> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct Hex32Bytes(#[serde(deserialize_with = "from_hex", serialize_with = "to_hex")] [u8; 32]);
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
|
||||
pub struct Hex32Bytes(#[serde(deserialize_with = "from_hex")] [u8; 32]);
|
||||
|
||||
impl Deref for Hex32Bytes {
|
||||
type Target = [u8; 32];
|
||||
@ -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()
|
||||
@ -124,13 +118,6 @@ where
|
||||
T::from_hex(&s).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn from_hex_array<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
T: FromHex + std::fmt::Debug,
|
||||
@ -151,7 +138,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 +147,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 +159,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 +172,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 +190,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 +201,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 +216,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 +227,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 +259,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 +274,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 +302,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 +327,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 +368,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 {
|
||||
@ -414,16 +388,3 @@ impl From<std::sync::mpsc::RecvError> for Error {
|
||||
Error::Mpsc
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ScriptStatus;
|
||||
|
||||
#[test]
|
||||
fn script_status_roundtrip() {
|
||||
let script_status: ScriptStatus = [1u8; 32].into();
|
||||
let script_status_json = serde_json::to_string(&script_status).unwrap();
|
||||
let script_status_back = serde_json::from_str(&script_status_json).unwrap();
|
||||
assert_eq!(script_status, script_status_back);
|
||||
}
|
||||
}
|
||||
|
||||
43
src/utils.rs
43
src/utils.rs
@ -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()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user