Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2fdc5ccf8 | ||
|
|
df6675dc09 | ||
|
|
4b98565ee2 | ||
|
|
5d7be37136 | ||
|
|
761796c94a | ||
|
|
5dc4bb6360 | ||
|
|
e31cf4773b | ||
|
|
c2656995fc | ||
|
|
b185259561 | ||
|
|
80bf744a70 | ||
|
|
0e28021b3e | ||
|
|
980fa4afd6 | ||
|
|
b20d41ace8 | ||
|
|
24d07e2cac | ||
|
|
fbffdfe65e | ||
|
|
dd13e4e422 | ||
|
|
1372898ff2 | ||
|
|
f24458248a | ||
|
|
74ac97cfe4 | ||
|
|
7de4cb758d | ||
|
|
ed9784ee79 | ||
|
|
e14f66b2f1 | ||
|
|
862280a882 | ||
|
|
a3488f4ff9 | ||
|
|
0ee0a6ad7a | ||
|
|
6721929f22 | ||
|
|
372eda9455 | ||
|
|
eb38c038b2 | ||
|
|
83747b131b | ||
|
|
0e459b6bd6 | ||
|
|
805ea0af30 | ||
|
|
3dc4daceaf | ||
|
|
5a476fdfd9 | ||
|
|
ec90685324 | ||
|
|
15f753f05f | ||
|
|
ed0cdae329 | ||
|
|
7ef3ff6873 |
21
.github/workflows/cont_integration.yml
vendored
21
.github/workflows/cont_integration.yml
vendored
@ -5,19 +5,19 @@ name: CI
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_ELECTRUM_SERVER: electrum.blockstream.info:50001
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable # STABLE
|
||||
- 1.63.0 # MSRV
|
||||
- 1.75.0 # MSRV
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@ -28,10 +28,6 @@ jobs:
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- name: Pin dependencies for MSRV
|
||||
if: matrix.rust == '1.63.0'
|
||||
run: |
|
||||
cargo update -p rustls --precise "0.23.17"
|
||||
- name: Test
|
||||
run: cargo test --verbose --all-features
|
||||
- name: Setup iptables for the timeout test
|
||||
@ -45,6 +41,7 @@ jobs:
|
||||
- run: cargo check --verbose --no-default-features --features=proxy,use-openssl
|
||||
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls
|
||||
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls-ring
|
||||
- run: cargo check --verbose --no-default-features --features=proxy,use-rustls,use-rustls-ring
|
||||
|
||||
fmt:
|
||||
name: Rust fmt
|
||||
@ -61,16 +58,14 @@ jobs:
|
||||
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
||||
|
||||
clippy_check:
|
||||
name: Rust clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 1.78.0
|
||||
toolchain: 1.90.0
|
||||
components: clippy
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.2.1
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features --all-targets -- -D warnings
|
||||
- run: cargo clippy --all-features --all-targets -- -D warnings
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.24.1]
|
||||
- Default to `ring` if multiple `rustls` features are set #183
|
||||
|
||||
## [0.24.0]
|
||||
- Use default `CryptoProvider` if available, otherwise install `rustls`'s `CryptoProvider` based on features #171
|
||||
- Add a new batch method for `blockchain.transaction.get_merkle` #170
|
||||
|
||||
## [0.23.1]
|
||||
- Fix batch request to Electrum servers out of order responses #160
|
||||
- Allow types that references to `ElectrumApi` to also implement it #163
|
||||
|
||||
## [0.23.0]
|
||||
|
||||
- Raise MSRV to `1.75` and bump `rustls` to `0.23.21` #159
|
||||
- Enforce min `rustls` version 0.23.19 to support MSRV with fix for RUSTSEC-2024-0399 #158
|
||||
|
||||
## [0.22.0]
|
||||
|
||||
- Updates the NoCertificateVerification implementation for the rustls::client::danger::ServerCertVerifier to use the rustls::SignatureScheme from CryptoProvider in use #150
|
||||
@ -39,8 +55,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Revert "errors if expecting headers notification but not subscribed" #115
|
||||
|
||||
[0.18.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.17.0...0.18.0
|
||||
[0.19.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.18.0...v0.19.0
|
||||
[0.20.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.19.0...v0.20.0
|
||||
[0.21.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.20.0...v0.21.0
|
||||
[0.22.0]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.21.0...v0.22.0
|
||||
[Unreleased]: https://github.com/bitcoindevkit/rust-electrum-client/compare/0.22.0...HEAD
|
||||
[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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "electrum-client"
|
||||
version = "0.22.0"
|
||||
version = "0.24.1"
|
||||
authors = ["Alekos Filini <alekos.filini@gmail.com>"]
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/bitcoindevkit/rust-electrum-client"
|
||||
@ -9,7 +9,7 @@ documentation = "https://docs.rs/electrum-client/"
|
||||
description = "Bitcoin Electrum client library. Supports plaintext, TLS and Onion servers."
|
||||
keywords = ["bitcoin", "electrum"]
|
||||
readme = "README.md"
|
||||
rust-version = "1.63.0"
|
||||
rust-version = "1.75.0"
|
||||
edition = "2021"
|
||||
|
||||
# loosely based on https://github.com/evgeniy-scherbina/rust-electrumx-client
|
||||
@ -26,7 +26,7 @@ serde_json = { version = "^1.0" }
|
||||
|
||||
# Optional dependencies
|
||||
openssl = { version = "0.10", optional = true }
|
||||
rustls = { version = "0.23", optional = true, default-features = false }
|
||||
rustls = { version = "0.23.21", optional = true, default-features = false }
|
||||
webpki-roots = { version = "0.25", optional = true }
|
||||
|
||||
byteorder = { version = "1.0", optional = true }
|
||||
|
||||
13
README.md
13
README.md
@ -5,18 +5,11 @@
|
||||
[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.63.0%2B-lightgrey.svg
|
||||
[Rust Blog]: https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html
|
||||
[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.63.0.
|
||||
|
||||
To build with the MSRV you will need to pin dependencies as follows:
|
||||
|
||||
```shell
|
||||
cargo update -p rustls --precise "0.23.17"
|
||||
```
|
||||
|
||||
This library should compile with any combination of features with Rust 1.75.0.
|
||||
|
||||
@ -1 +1 @@
|
||||
msrv="1.63.0"
|
||||
msrv="1.75.0"
|
||||
34
justfile
Normal file
34
justfile
Normal file
@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
406
src/api.rs
406
src/api.rs
@ -2,6 +2,7 @@
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::Deref;
|
||||
|
||||
use bitcoin::consensus::encode::{deserialize, serialize};
|
||||
use bitcoin::{block, Script, Transaction, Txid};
|
||||
@ -9,6 +10,178 @@ use bitcoin::{block, Script, Transaction, Txid};
|
||||
use crate::batch::Batch;
|
||||
use crate::types::*;
|
||||
|
||||
impl<E: Deref> ElectrumApi for E
|
||||
where
|
||||
E::Target: ElectrumApi,
|
||||
{
|
||||
fn raw_call(
|
||||
&self,
|
||||
method_name: &str,
|
||||
params: impl IntoIterator<Item = Param>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
(**self).raw_call(method_name, params)
|
||||
}
|
||||
|
||||
fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error> {
|
||||
(**self).batch_call(batch)
|
||||
}
|
||||
|
||||
fn block_headers_subscribe_raw(&self) -> Result<RawHeaderNotification, Error> {
|
||||
(**self).block_headers_subscribe_raw()
|
||||
}
|
||||
|
||||
fn block_headers_pop_raw(&self) -> Result<Option<RawHeaderNotification>, Error> {
|
||||
(**self).block_headers_pop_raw()
|
||||
}
|
||||
|
||||
fn block_header_raw(&self, height: usize) -> Result<Vec<u8>, Error> {
|
||||
(**self).block_header_raw(height)
|
||||
}
|
||||
|
||||
fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error> {
|
||||
(**self).block_headers(start_height, count)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
|
||||
(**self).estimate_fee(number)
|
||||
}
|
||||
|
||||
fn relay_fee(&self) -> Result<f64, Error> {
|
||||
(**self).relay_fee()
|
||||
}
|
||||
|
||||
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
|
||||
(**self).script_subscribe(script)
|
||||
}
|
||||
|
||||
fn batch_script_subscribe<'s, I>(&self, scripts: I) -> Result<Vec<Option<ScriptStatus>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
(**self).batch_script_subscribe(scripts)
|
||||
}
|
||||
|
||||
fn script_unsubscribe(&self, script: &Script) -> Result<bool, Error> {
|
||||
(**self).script_unsubscribe(script)
|
||||
}
|
||||
|
||||
fn script_pop(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
|
||||
(**self).script_pop(script)
|
||||
}
|
||||
|
||||
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes, Error> {
|
||||
(**self).script_get_balance(script)
|
||||
}
|
||||
|
||||
fn batch_script_get_balance<'s, I>(&self, scripts: I) -> Result<Vec<GetBalanceRes>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
(**self).batch_script_get_balance(scripts)
|
||||
}
|
||||
|
||||
fn script_get_history(&self, script: &Script) -> Result<Vec<GetHistoryRes>, Error> {
|
||||
(**self).script_get_history(script)
|
||||
}
|
||||
|
||||
fn batch_script_get_history<'s, I>(&self, scripts: I) -> Result<Vec<Vec<GetHistoryRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
(**self).batch_script_get_history(scripts)
|
||||
}
|
||||
|
||||
fn script_list_unspent(&self, script: &Script) -> Result<Vec<ListUnspentRes>, Error> {
|
||||
(**self).script_list_unspent(script)
|
||||
}
|
||||
|
||||
fn batch_script_list_unspent<'s, I>(
|
||||
&self,
|
||||
scripts: I,
|
||||
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'s Script>,
|
||||
{
|
||||
(**self).batch_script_list_unspent(scripts)
|
||||
}
|
||||
|
||||
fn transaction_get_raw(&self, txid: &Txid) -> Result<Vec<u8>, Error> {
|
||||
(**self).transaction_get_raw(txid)
|
||||
}
|
||||
|
||||
fn batch_transaction_get_raw<'t, I>(&self, txids: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<&'t Txid>,
|
||||
{
|
||||
(**self).batch_transaction_get_raw(txids)
|
||||
}
|
||||
|
||||
fn batch_block_header_raw<I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<u32>,
|
||||
{
|
||||
(**self).batch_block_header_raw(heights)
|
||||
}
|
||||
|
||||
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<usize>,
|
||||
{
|
||||
(**self).batch_estimate_fee(numbers)
|
||||
}
|
||||
|
||||
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
|
||||
(**self).transaction_broadcast_raw(raw_tx)
|
||||
}
|
||||
|
||||
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
|
||||
(**self).transaction_get_merkle(txid, height)
|
||||
}
|
||||
|
||||
fn batch_transaction_get_merkle<I>(
|
||||
&self,
|
||||
txids_and_heights: I,
|
||||
) -> Result<Vec<GetMerkleRes>, Error>
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::Item: Borrow<(Txid, usize)>,
|
||||
{
|
||||
(**self).batch_transaction_get_merkle(txids_and_heights)
|
||||
}
|
||||
|
||||
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
|
||||
(**self).txid_from_pos(height, tx_pos)
|
||||
}
|
||||
|
||||
fn txid_from_pos_with_merkle(
|
||||
&self,
|
||||
height: usize,
|
||||
tx_pos: usize,
|
||||
) -> Result<TxidFromPosRes, Error> {
|
||||
(**self).txid_from_pos_with_merkle(height, tx_pos)
|
||||
}
|
||||
|
||||
fn server_features(&self) -> Result<ServerFeaturesRes, Error> {
|
||||
(**self).server_features()
|
||||
}
|
||||
|
||||
fn ping(&self) -> Result<(), Error> {
|
||||
(**self).ping()
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-calls")]
|
||||
fn calls_made(&self) -> Result<usize, Error> {
|
||||
(**self).calls_made()
|
||||
}
|
||||
}
|
||||
|
||||
/// API calls exposed by an Electrum client
|
||||
pub trait ElectrumApi {
|
||||
/// Gets the block header for height `height`.
|
||||
@ -200,6 +373,17 @@ 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>;
|
||||
|
||||
@ -222,3 +406,225 @@ 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>>();
|
||||
}
|
||||
}
|
||||
|
||||
13
src/batch.rs
13
src/batch.rs
@ -62,6 +62,17 @@ impl Batch {
|
||||
.push((String::from("blockchain.transaction.get"), params));
|
||||
}
|
||||
|
||||
/// Add one `blockchain.transaction.get_merkle` request to the batch queue
|
||||
pub fn transaction_get_merkle(&mut self, tx_hash_and_height: &(Txid, usize)) {
|
||||
let (tx_hash, height) = tx_hash_and_height;
|
||||
let params = vec![
|
||||
Param::String(format!("{:x}", tx_hash)),
|
||||
Param::Usize(*height),
|
||||
];
|
||||
self.calls
|
||||
.push((String::from("blockchain.transaction.get_merkle"), params));
|
||||
}
|
||||
|
||||
/// Add one `blockchain.estimatefee` request to the batch queue
|
||||
pub fn estimate_fee(&mut self, number: usize) {
|
||||
let params = vec![Param::Usize(number)];
|
||||
@ -77,7 +88,7 @@ impl Batch {
|
||||
}
|
||||
|
||||
/// Returns an iterator on the batch
|
||||
pub fn iter(&self) -> BatchIter {
|
||||
pub fn iter(&self) -> BatchIter<'_> {
|
||||
BatchIter {
|
||||
batch: self,
|
||||
index: 0,
|
||||
|
||||
@ -327,6 +327,22 @@ 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)
|
||||
@ -432,7 +448,9 @@ mod tests {
|
||||
let now = Instant::now();
|
||||
let client = Client::from_config(
|
||||
&endpoint,
|
||||
crate::config::ConfigBuilder::new().timeout(Some(5)).build(),
|
||||
crate::config::ConfigBuilder::new()
|
||||
.timeout(Some(Duration::from_secs(5)))
|
||||
.build(),
|
||||
);
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
|
||||
@ -55,8 +55,8 @@ impl ConfigBuilder {
|
||||
}
|
||||
|
||||
/// Sets the timeout
|
||||
pub fn timeout(mut self, timeout: Option<u8>) -> Self {
|
||||
self.config.timeout = timeout.map(|t| Duration::from_secs(t as u64));
|
||||
pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
|
||||
self.config.timeout = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
//! This module contains the definition of the raw client that wraps the transport method
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::mem::drop;
|
||||
use std::net::{TcpStream, ToSocketAddrs};
|
||||
@ -406,6 +406,29 @@ impl RawClient<ElectrumSslStream> {
|
||||
) -> Result<Self, Error> {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
if rustls::crypto::CryptoProvider::get_default().is_none() {
|
||||
// We install a crypto provider depending on the set feature.
|
||||
#[cfg(all(feature = "use-rustls", not(feature = "use-rustls-ring")))]
|
||||
rustls::crypto::CryptoProvider::install_default(
|
||||
rustls::crypto::aws_lc_rs::default_provider(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::CouldNotCreateConnection(rustls::Error::General(
|
||||
"Failed to install CryptoProvider".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
#[cfg(feature = "use-rustls-ring")]
|
||||
rustls::crypto::CryptoProvider::install_default(
|
||||
rustls::crypto::ring::default_provider(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::CouldNotCreateConnection(rustls::Error::General(
|
||||
"Failed to install CryptoProvider".to_string(),
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
let builder = ClientConfig::builder();
|
||||
|
||||
let config = if validate_domain {
|
||||
@ -426,7 +449,7 @@ impl RawClient<ElectrumSslStream> {
|
||||
builder
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(std::sync::Arc::new(
|
||||
#[cfg(feature = "use-rustls")]
|
||||
#[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()),
|
||||
@ -539,11 +562,10 @@ 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. 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.
|
||||
// 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.
|
||||
if self.waiting_map.lock()?.get(&until_message).is_none() {
|
||||
return Err(Error::CouldntLockReader);
|
||||
}
|
||||
@ -762,12 +784,10 @@ 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 = BTreeSet::new();
|
||||
let mut missing_responses = Vec::new();
|
||||
let mut answers = BTreeMap::new();
|
||||
|
||||
// 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();
|
||||
// Add our listener to the map before we send the request
|
||||
|
||||
for (method, params) in batch.iter() {
|
||||
let req = Request::new_id(
|
||||
@ -775,9 +795,12 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
method,
|
||||
params.to_vec(),
|
||||
);
|
||||
missing_responses.insert(req.id);
|
||||
// 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));
|
||||
|
||||
self.waiting_map.lock()?.insert(req.id, sender.clone());
|
||||
self.waiting_map.lock()?.insert(req.id, sender);
|
||||
|
||||
raw.append(&mut serde_json::to_vec(&req)?);
|
||||
raw.extend_from_slice(b"\n");
|
||||
@ -796,8 +819,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
|
||||
self.increment_calls();
|
||||
|
||||
for req_id in missing_responses.iter() {
|
||||
match self.recv(&receiver, *req_id) {
|
||||
for (req_id, receiver) 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
|
||||
@ -805,7 +828,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);
|
||||
@ -1102,6 +1125,17 @@ 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(
|
||||
@ -1190,12 +1224,32 @@ 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_eq!(resp, 0.00001);
|
||||
assert!(resp > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1428,6 +1482,98 @@ mod test {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_transaction_get_merkle() {
|
||||
use bitcoin::Txid;
|
||||
|
||||
struct TestCase {
|
||||
txid: Txid,
|
||||
block_height: usize,
|
||||
exp_pos: usize,
|
||||
exp_bytes: [u8; 32],
|
||||
}
|
||||
|
||||
let client = RawClient::new(get_test_server(), None).unwrap();
|
||||
|
||||
let test_cases: Vec<TestCase> = vec![
|
||||
TestCase {
|
||||
txid: Txid::from_str(
|
||||
"1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d",
|
||||
)
|
||||
.unwrap(),
|
||||
block_height: 630000,
|
||||
exp_pos: 68,
|
||||
exp_bytes: [
|
||||
34, 65, 51, 64, 49, 139, 115, 189, 185, 246, 70, 225, 168, 193, 217, 195, 47,
|
||||
66, 179, 240, 153, 24, 114, 215, 144, 196, 212, 41, 39, 155, 246, 25,
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
txid: Txid::from_str(
|
||||
"70a8639bc9b743c0610d1231103a2f8e99f4a25670946b91f16c55a5373b37d1",
|
||||
)
|
||||
.unwrap(),
|
||||
block_height: 630001,
|
||||
exp_pos: 25,
|
||||
exp_bytes: [
|
||||
169, 100, 34, 99, 168, 101, 25, 168, 184, 90, 77, 50, 151, 245, 130, 101, 193,
|
||||
229, 136, 128, 63, 110, 241, 19, 242, 59, 184, 137, 245, 249, 188, 110,
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
txid: Txid::from_str(
|
||||
"a0db149ace545beabbd87a8d6b20ffd6aa3b5a50e58add49a3d435f898c272cf",
|
||||
)
|
||||
.unwrap(),
|
||||
block_height: 840000,
|
||||
exp_pos: 0,
|
||||
exp_bytes: [
|
||||
43, 184, 95, 75, 0, 75, 230, 218, 84, 247, 102, 193, 124, 30, 133, 81, 135, 50,
|
||||
113, 18, 194, 49, 239, 47, 243, 94, 186, 208, 234, 103, 198, 158,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let txids_and_heights: Vec<(Txid, usize)> = test_cases
|
||||
.iter()
|
||||
.map(|case| (case.txid, case.block_height))
|
||||
.collect();
|
||||
|
||||
let resp = client
|
||||
.batch_transaction_get_merkle(&txids_and_heights)
|
||||
.unwrap();
|
||||
|
||||
for (i, (res, test_case)) in resp.iter().zip(test_cases).enumerate() {
|
||||
assert_eq!(res.block_height, test_case.block_height);
|
||||
assert_eq!(res.pos, test_case.exp_pos);
|
||||
assert_eq!(res.merkle.len(), 12);
|
||||
assert_eq!(res.merkle[0], test_case.exp_bytes);
|
||||
|
||||
// Check we can verify the merkle proof validity, but fail if we supply wrong data.
|
||||
let block_header = client.block_header(res.block_height).unwrap();
|
||||
assert!(utils::validate_merkle_proof(
|
||||
&txids_and_heights[i].0,
|
||||
&block_header.merkle_root,
|
||||
res
|
||||
));
|
||||
|
||||
let mut fail_res = res.clone();
|
||||
fail_res.pos = 13;
|
||||
assert!(!utils::validate_merkle_proof(
|
||||
&txids_and_heights[i].0,
|
||||
&block_header.merkle_root,
|
||||
&fail_res
|
||||
));
|
||||
|
||||
let fail_block_header = client.block_header(res.block_height + 1).unwrap();
|
||||
assert!(!utils::validate_merkle_proof(
|
||||
&txids_and_heights[i].0,
|
||||
&fail_block_header.merkle_root,
|
||||
res
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_txid_from_pos() {
|
||||
use bitcoin::Txid;
|
||||
|
||||
@ -99,7 +99,7 @@ impl ToTargetAddr for (Ipv6Addr, u16) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToTargetAddr for (&'a str, u16) {
|
||||
impl ToTargetAddr for (&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<'a> ToTargetAddr for (&'a str, u16) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToTargetAddr for &'a str {
|
||||
impl ToTargetAddr for &str {
|
||||
fn to_target_addr(&self) -> io::Result<TargetAddr> {
|
||||
// try to parse as an IP first
|
||||
if let Ok(addr) = self.parse::<SocketAddrV4>() {
|
||||
|
||||
@ -18,12 +18,7 @@ fn read_response(socket: &mut TcpStream) -> io::Result<SocketAddrV4> {
|
||||
|
||||
match response.read_u8()? {
|
||||
90 => {}
|
||||
91 => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"request rejected or failed",
|
||||
))
|
||||
}
|
||||
91 => return Err(io::Error::other("request rejected or failed")),
|
||||
92 => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
@ -148,7 +143,7 @@ impl Read for Socks4Stream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for &'a Socks4Stream {
|
||||
impl Read for &Socks4Stream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
(&self.socket).read(buf)
|
||||
}
|
||||
@ -164,7 +159,7 @@ impl Write for Socks4Stream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for &'a Socks4Stream {
|
||||
impl Write for &Socks4Stream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
(&self.socket).write(buf)
|
||||
}
|
||||
|
||||
@ -37,10 +37,7 @@ fn read_addr<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
|
||||
ip, port, 0, 0,
|
||||
))))
|
||||
}
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"unsupported address type",
|
||||
)),
|
||||
_ => Err(io::Error::other("unsupported address type")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,35 +51,15 @@ fn read_response(socket: &mut TcpStream) -> io::Result<TargetAddr> {
|
||||
|
||||
match socket.read_u8()? {
|
||||
0 => {}
|
||||
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")),
|
||||
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")),
|
||||
}
|
||||
|
||||
if socket.read_u8()? != 0 {
|
||||
@ -135,7 +112,7 @@ enum Authentication<'a> {
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> Authentication<'a> {
|
||||
impl Authentication<'_> {
|
||||
fn id(&self) -> u8 {
|
||||
match *self {
|
||||
Authentication::Password { .. } => 2,
|
||||
@ -227,14 +204,11 @@ impl Socks5Stream {
|
||||
}
|
||||
|
||||
if selected_method == 0xff {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"no acceptable auth methods",
|
||||
));
|
||||
return Err(io::Error::other("no acceptable auth methods"));
|
||||
}
|
||||
|
||||
if selected_method != auth.id() && selected_method != Authentication::None.id() {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method"));
|
||||
return Err(io::Error::other("unknown auth method"));
|
||||
}
|
||||
|
||||
match *auth {
|
||||
@ -329,7 +303,7 @@ impl Read for Socks5Stream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for &'a Socks5Stream {
|
||||
impl Read for &Socks5Stream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
(&self.socket).read(buf)
|
||||
}
|
||||
@ -345,7 +319,7 @@ impl Write for Socks5Stream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for &'a Socks5Stream {
|
||||
impl Write for &Socks5Stream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
(&self.socket).write(buf)
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use bitcoin::Txid;
|
||||
/// otherwise.
|
||||
///
|
||||
/// [`transaction_get_merkle`]: crate::ElectrumApi::transaction_get_merkle
|
||||
/// [`BlockHeader`]: bitcoin::BlockHeader
|
||||
/// [`BlockHeader`]: bitcoin::block::Header
|
||||
pub fn validate_merkle_proof(
|
||||
txid: &Txid,
|
||||
merkle_root: &TxMerkleNode,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user