Skip to content

Commit

Permalink
Implements retry, timeout, socks credential and others fixes
Browse files Browse the repository at this point in the history
Retry works by checking if an non-protocol error happened during the call, if so an attempt to take a write lock on the client is made and a new client attempted
if both operationare succesfull the old_client is substituted with the new one.

Timeout is a single parameter for connect,read and write TCP timeouts, special handling is required for the following situation: cannot set a timeout with a proxy and cannot set timeout with multipl SocketAddrs

Since configurations parameter grow, a Config struct with a builder has been introduced

Some new errors variant has been introduced

Errors in reading input in the reader thread now warns other eventual threads with ChannelMessage::Error

Add Config to Client for multiple options, implements retry
  • Loading branch information
RCasatta committed Nov 24, 2020
1 parent c1f4e9d commit 39a87c1
Show file tree
Hide file tree
Showing 11 changed files with 471 additions and 120 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
on: [push, pull_request]

name: CI

jobs:

test-fmt:
name: Test
runs-on: ubuntu-20.04
env:
TEST_ELECTRUM_SERVER: electrum.blockstream.info:50001
#TEST_ELECTRUM_SERVER: bitcoin.aranguren.org:50001
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- 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
- run: cargo check --verbose --features=use-openssl
- run: cargo check --verbose --no-default-features --features=proxy
- run: cargo check --verbose --no-default-features --features=minimal
- 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
2 changes: 1 addition & 1 deletion examples/plaintext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extern crate electrum_client;
use electrum_client::{Client, ElectrumApi};

fn main() {
let client = Client::new("tcp://electrum.blockstream.info:50001", None).unwrap();
let client = Client::new("tcp://electrum.blockstream.info:50001").unwrap();
let res = client.server_features();
println!("{:#?}", res);
}
2 changes: 1 addition & 1 deletion examples/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extern crate electrum_client;
use electrum_client::{Client, ElectrumApi};

fn main() {
let client = Client::new("ssl://electrum.blockstream.info:50002", None).unwrap();
let client = Client::new("ssl://electrum.blockstream.info:50002").unwrap();
let res = client.server_features();
println!("{:#?}", res);
}
10 changes: 6 additions & 4 deletions examples/tor.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
extern crate electrum_client;

use electrum_client::{Client, ElectrumApi};
use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config};

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)).unwrap().build();

let client = Client::new("tcp://explorernuoc63nb.onion:110", Some("127.0.0.1:9050")).unwrap();
let client = Client::from_config("tcp://explorernuoc63nb.onion:110", config.clone()).unwrap();
let res = client.server_features();
println!("{:#?}", res);

// works both with onion v2/v3 (if your Tor supports them)
let client = Client::new(
let client = Client::from_config(
"tcp://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion:110",
Some("127.0.0.1:9050"),
config,
)
.unwrap();
let res = client.server_features();
Expand Down
24 changes: 12 additions & 12 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +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<Item = &'t Txid>,
I: IntoIterator<Item = &'t Txid> + Clone,
{
self.batch_transaction_get_raw(txids)?
.iter()
Expand All @@ -49,9 +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<'s, I>(&self, heights: I) -> Result<Vec<BlockHeader>, Error>
fn batch_block_header<I>(&self, heights: I) -> Result<Vec<BlockHeader>, Error>
where
I: IntoIterator<Item = u32>,
I: IntoIterator<Item = u32> + Clone,
{
self.batch_block_header_raw(heights)?
.iter()
Expand All @@ -68,7 +68,7 @@ pub trait ElectrumApi {
/// Execute a queue of calls stored in a [`Batch`](../batch/struct.Batch.html) struct. Returns
/// `Ok()` **only if** all of the calls are successful. The order of the JSON `Value`s returned
/// reflects the order in which the calls were made on the `Batch` struct.
fn batch_call(&self, batch: Batch) -> Result<Vec<serde_json::Value>, Error>;
fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error>;

/// Subscribes to notifications for new block headers, by sending a `blockchain.headers.subscribe` call and
/// returns the current tip as raw bytes instead of deserializing them.
Expand Down Expand Up @@ -118,7 +118,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<Item = &'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>;
Expand All @@ -128,7 +128,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<Item = &'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>;
Expand All @@ -141,7 +141,7 @@ pub trait ElectrumApi {
scripts: I,
) -> Result<Vec<Vec<ListUnspentRes>>, Error>
where
I: IntoIterator<Item = &'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>;
Expand All @@ -151,22 +151,22 @@ 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<Item = &'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<'s, I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
fn batch_block_header_raw<I>(&self, heights: I) -> Result<Vec<Vec<u8>>, Error>
where
I: IntoIterator<Item = u32>;
I: IntoIterator<Item = u32> + Clone;

/// Batch version of [`estimate_fee`](#method.estimate_fee).
///
/// Takes a list of `numbers` of blocks and returns a list of fee required in
/// **Satoshis per kilobyte** to confirm a transaction in the given number of blocks.
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
where
I: IntoIterator<Item = 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>;
Expand Down
23 changes: 23 additions & 0 deletions src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ impl Batch {
self.calls
.push((String::from("blockchain.block.header"), params));
}

/// Returns an iterator on the batch
pub fn iter(&self) -> BatchIter {
BatchIter {
batch: self,
index: 0,
}
}
}

impl std::iter::IntoIterator for Batch {
Expand All @@ -74,6 +82,21 @@ impl std::iter::IntoIterator for Batch {
}
}

pub struct BatchIter<'a> {
batch: &'a Batch,
index: usize,
}

impl<'a> std::iter::Iterator for BatchIter<'a> {
type Item = &'a (String, Vec<Param>);

fn next(&mut self) -> Option<Self::Item> {
let val = self.batch.calls.get(self.index);
self.index += 1;
val
}
}

impl std::default::Default for Batch {
fn default() -> Self {
Batch { calls: Vec::new() }
Expand Down
Loading

0 comments on commit 39a87c1

Please sign in to comment.