Skip to content

Commit 9aa035f

Browse files
committed
Import electrsd
The `corepc` repo is for all things integration testing. Also it provides `bitreq` (HTTP crate), and `corepc-types` (Bitcoin Core JSONRPC types). When integration testing some users (eg `bdk`) use `electrsd` along side `corepc-node` (formerly `bitcoind`). `electrsd` depends on `corepc-node` and every time we release `corepc-node` we have to do a release of `electrsd`. Having them in separate repositories causes friction and makes additional work. Instead we can put them together to hopefully make everyone's life easier (well mine and Ricardo's anyway). Note that `bitcoind` was written by Ricardo. I re-named it, a move I'm now regretting because of the original symmetry with `electrsd`. This import has the blessing of Ricardo and needs an explicit ACK from him before merging. When the CODEOWNERS file is added (#396) it should include him as owner of this crate. Import the `electrsd` crate from https://github.com/RCasatta/electrsd/ While HEAD is at commit hash: dc88d81520c9ec507fc830b88a3b92a034dbaf93 `dc88d81: bump version 0.36.0 -> 0.36.1`
1 parent e022b7c commit 9aa035f

File tree

10 files changed

+975
-1
lines changed

10 files changed

+975
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = [ "bitreq", "client", "jsonrpc", "node", "types"]
2+
members = [ "bitreq", "client", "jsonrpc", "node", "types", "electrsd"]
33
exclude = ["integration_test", "verify"]
44
resolver = "2"
55

electrsd/Cargo.toml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[package]
2+
name = "electrsd"
3+
version = "0.36.1"
4+
authors = ["Riccardo Casatta <[email protected]>"]
5+
description = "Utility to run a regtest electrs process, useful in integration testing environment"
6+
repository = "https://github.com/RCasatta/electrsd"
7+
documentation = "https://docs.rs/elecrtsd/"
8+
license = "MIT"
9+
edition = "2018"
10+
categories = ["cryptography::cryptocurrencies", "development-tools::testing"]
11+
12+
[dependencies]
13+
corepc-node = { version = "0.10.0" }
14+
corepc-client = { version = "0.10.0" }
15+
electrum-client = { version = "0.24.0", default-features = false }
16+
log = { version = "0.4" }
17+
18+
[target.'cfg(not(windows))'.dependencies]
19+
nix = { version = "0.25.0" }
20+
21+
[dev-dependencies]
22+
env_logger = { version = "0.10" }
23+
24+
[build-dependencies]
25+
bitcoin_hashes = { version = "0.14", optional = true }
26+
zip = { version = "0.6", default-features = false, optional = true, features = [
27+
"bzip2",
28+
"deflate",
29+
] }
30+
minreq = { version = "2.9.0", default-features = false, optional = true, features = [
31+
"https",
32+
] }
33+
34+
[features]
35+
legacy = []
36+
37+
# download is not supposed to be used directly only through selecting one of the version feature
38+
download = ["bitcoin_hashes", "zip", "minreq"]
39+
40+
esplora_a33e97e1 = ["download", "legacy"]
41+
electrs_0_8_10 = ["download"]
42+
electrs_0_9_1 = ["download"]
43+
electrs_0_9_11 = ["download"]
44+
electrs_0_10_6 = ["download"]
45+
46+
corepc-node_29_0 = ["corepc-node/download", "corepc-node/29_0"]
47+
corepc-node_28_2 = ["corepc-node/download", "corepc-node/28_2"]
48+
corepc-node_27_2 = ["corepc-node/download", "corepc-node/27_2"]
49+
corepc-node_26_2 = ["corepc-node/download", "corepc-node/26_2"]
50+
corepc-node_25_2 = ["corepc-node/download", "corepc-node/25_2"]
51+
corepc-node_24_2 = ["corepc-node/download", "corepc-node/24_2"]
52+
corepc-node_23_1 = ["corepc-node/download", "corepc-node/23_2"]
53+
corepc-node_22_1 = ["corepc-node/download", "corepc-node/22_1"]
54+
corepc-node_0_21_2 = ["corepc-node/download", "corepc-node/0_21_2"]
55+
corepc-node_0_20_2 = ["corepc-node/download", "corepc-node/0_20_2"]
56+
corepc-node_0_19_1 = ["corepc-node/download", "corepc-node/0_19_1"]
57+
corepc-node_0_18_1 = ["corepc-node/download", "corepc-node/0_18_1"]
58+
corepc-node_0_17_2 = ["corepc-node/download", "corepc-node/0_17_2"]

electrsd/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Riccardo Casatta
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

electrsd/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
[![MIT license](https://img.shields.io/github/license/RCasatta/electrsd)](https://github.com/RCasatta/electrsd/blob/master/LICENSE)
2+
[![Crates](https://img.shields.io/crates/v/electrsd.svg)](https://crates.io/crates/electrsd)
3+
4+
# Electrsd
5+
6+
Utility to run a regtest [electrs](https://github.com/romanz/electrs/) process connected to a given [bitcoind](https://github.com/RCasatta/bitcoind) instance,
7+
useful in integration testing environment.
8+
9+
```rust
10+
let bitcoind = bitcoind::BitcoinD::new("/usr/local/bin/bitcoind").unwrap();
11+
let electrsd = electrsd::ElectrsD::new("/usr/local/bin/electrs", bitcoind).unwrap();
12+
let header = electrsd.client.block_headers_subscribe().unwrap();
13+
assert_eq!(header.height, 0);
14+
```
15+
16+
## Automatic binaries download
17+
18+
In your project Cargo.toml, activate the following features
19+
20+
```yml
21+
electrsd = { version= "0.23", features = ["corepc-node_23_1", "electrs_0_9_1"] }
22+
```
23+
24+
Then use it:
25+
26+
```rust
27+
let bitcoind_exe = bitcoind::downloaded_exe_path().expect("bitcoind version feature must be enabled");
28+
let bitcoind = bitcoind::BitcoinD::new(bitcoind_exe).unwrap();
29+
let electrs_exe = electrsd::downloaded_exe_path().expect("electrs version feature must be enabled");
30+
let electrsd = electrsd::ElectrsD::new(electrs_exe, bitcoind).unwrap();
31+
```
32+
33+
When the `ELECTRSD_DOWNLOAD_ENDPOINT`/`BITCOIND_DOWNLOAD_ENDPOINT` environment variables are set,
34+
`electrsd`/`bitcoind` will try to download the binaries from the given endpoints.
35+
36+
When you don't use the auto-download feature you have the following options:
37+
38+
- have `electrs` executable in the `PATH`
39+
- provide the `electrs` executable via the `ELECTRS_EXEC` env var
40+
41+
```rust
42+
if let Ok(exe_path) = electrsd::exe_path() {
43+
let electrsd = electrsd::electrsD::new(exe_path).unwrap();
44+
}
45+
```
46+
47+
Startup options could be configured via the `Conf` struct using `electrsD::with_conf` or `electrsD::from_downloaded_with_conf`.
48+
49+
## Nix
50+
51+
For determinisim, in nix you cannot hit the internet within the `build.rs`. Moreover, some downstream crates cannot remove the auto-download feature from their dev-deps. In this case you can set the `ELECTRSD_SKIP_DOWNLOAD` env var and provide the electrs executable in the `PATH` (or skip the test execution).
52+
53+
## Issues with traditional approach
54+
55+
I used integration testing based on external bash script launching needed external processes, there are many issues with this approach like:
56+
57+
* External script may interfere with local development environment https://github.com/rust-bitcoin/rust-bitcoincore-rpc/blob/200fc8247c1896709a673b82a89ca0da5e7aa2ce/integration_test/run.sh#L9
58+
* Use of a single huge test to test everything https://github.com/rust-bitcoin/rust-bitcoincore-rpc/blob/200fc8247c1896709a673b82a89ca0da5e7aa2ce/integration_test/src/main.rs#L122-L203
59+
* If test are separated, a failing test may fail to leave a clean situation, causing other test to fail (because of the initial situation, not a real failure)
60+
* bash script are hard, especially support different OS and versions
61+
62+
## Features
63+
64+
* electrsd use a temporary directory as db dir
65+
* A free port is asked to the OS (a very low probability race condition is still possible)
66+
* The process is killed when the struct goes out of scope no matter how the test finishes
67+
* Automatically download `electrs` executable with enabled features. Since there are no official binaries, they are built using the [manual workflow](.github/workflows/build_electrs.yml) under this project. Supported version are:
68+
* [electrs 0.10.6](https://github.com/romanz/electrs/releases/tag/v0.10.6) (feature=electrs_0_10_6)
69+
* [electrs 0.9.11](https://github.com/romanz/electrs/releases/tag/v0.9.11) (feature=electrs_0_9_11)
70+
* [electrs 0.9.1](https://github.com/romanz/electrs/releases/tag/v0.9.1) (feature=electrs_0_9_1)
71+
* [electrs 0.8.10](https://github.com/romanz/electrs/releases/tag/v0.8.10) (feature=electrs_0_8_10)
72+
* [electrs esplora](https://github.com/Blockstream/electrs/tree/a33e97e1a1fc63fa9c20a116bb92579bbf43b254) (feature=esplora_a33e97e1)
73+
74+
Thanks to these features every `#[test]` could easily run isolated with its own environment
75+
76+
## Deprecations
77+
78+
- Starting from version `0.26` the env var `ELECTRS_EXE` is deprecated in favor of `ELECTRS_EXEC`.
79+
80+
81+
## Used by
82+
83+
* [bdk](https://github.com/bitcoindevkit/bdk)
84+
* [BEWallet](https://github.com/LeoComandini/BEWallet)
85+
* [gdk rust](https://github.com/Blockstream/gdk/blob/master/subprojects/gdk_rust/)
86+
* [lwk](https://github.com/Blockstream/lwk)

electrsd/build.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#[cfg(not(feature = "download"))]
2+
fn main() {}
3+
4+
#[cfg(feature = "download")]
5+
fn main() {
6+
download::download()
7+
}
8+
9+
#[cfg(feature = "download")]
10+
mod download {
11+
use bitcoin_hashes::{sha256, Hash};
12+
use std::fs::File;
13+
use std::io::{BufRead, BufReader, Cursor};
14+
use std::os::unix::fs::PermissionsExt;
15+
use std::path::Path;
16+
use std::str::FromStr;
17+
18+
include!("src/versions.rs");
19+
20+
const GITHUB_URL: &str =
21+
"https://github.com/RCasatta/electrsd/releases/download/electrs_releases";
22+
23+
fn get_expected_sha256(filename: &str) -> Result<sha256::Hash, ()> {
24+
let file = File::open("sha256").map_err(|_| ())?;
25+
for line in BufReader::new(file).lines().flatten() {
26+
let tokens: Vec<_> = line.split(" ").collect();
27+
if tokens.len() == 2 && filename == tokens[1] {
28+
return sha256::Hash::from_str(tokens[0]).map_err(|_| ());
29+
}
30+
}
31+
Err(())
32+
}
33+
34+
pub fn download() {
35+
if std::env::var_os("ELECTRSD_SKIP_DOWNLOAD").is_some() {
36+
return;
37+
}
38+
39+
if !HAS_FEATURE {
40+
return;
41+
}
42+
let download_filename_without_extension = electrs_name();
43+
let download_filename = format!("{}.zip", download_filename_without_extension);
44+
dbg!(&download_filename);
45+
let expected_hash = get_expected_sha256(&download_filename).unwrap();
46+
let out_dir = std::env::var_os("OUT_DIR").unwrap();
47+
let electrs_exe_home = Path::new(&out_dir).join("electrs");
48+
let destination_filename = electrs_exe_home
49+
.join(&download_filename_without_extension)
50+
.join("electrs");
51+
52+
dbg!(&destination_filename);
53+
54+
if !destination_filename.exists() {
55+
println!(
56+
"filename:{} version:{} hash:{}",
57+
download_filename, VERSION, expected_hash
58+
);
59+
60+
let download_endpoint =
61+
std::env::var("ELECTRSD_DOWNLOAD_ENDPOINT").unwrap_or(GITHUB_URL.to_string());
62+
let url = format!("{}/{}", download_endpoint, download_filename);
63+
64+
let downloaded_bytes = minreq::get(url).send().unwrap().into_bytes();
65+
66+
let downloaded_hash = sha256::Hash::hash(&downloaded_bytes);
67+
assert_eq!(expected_hash, downloaded_hash);
68+
let cursor = Cursor::new(downloaded_bytes);
69+
70+
let mut archive = zip::ZipArchive::new(cursor).unwrap();
71+
let mut file = archive.by_index(0).unwrap();
72+
std::fs::create_dir_all(destination_filename.parent().unwrap()).unwrap();
73+
let mut outfile = std::fs::File::create(&destination_filename).unwrap();
74+
75+
std::io::copy(&mut file, &mut outfile).unwrap();
76+
std::fs::set_permissions(
77+
&destination_filename,
78+
std::fs::Permissions::from_mode(0o755),
79+
)
80+
.unwrap();
81+
}
82+
}
83+
}

electrsd/sha256

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2d5ff149e8a2482d3658e9b386830dfc40c8fbd7c175ca7cbac58240a9505bcd electrs_macos_esplora_a33e97e1a1fc63fa9c20a116bb92579bbf43b254.zip
2+
865e26a96e8df77df01d96f2f569dcf9622fc87a8d99a9b8fe30861a4db9ddf1 electrs_linux_esplora_a33e97e1a1fc63fa9c20a116bb92579bbf43b254.zip
3+
0459d493d399bdb9ef145c84125c3cd26c1993a48efe59fa9d3fa13a03b2f555 electrs_linux_v0.8.10.zip
4+
48c857ca953ea66ee31c4da5a801298c85815e792ab57291107e77a4871d5421 electrs_macos_v0.8.10.zip
5+
fee5cc9b6c8bbd3adc45c63c881844d948c1b4dd6817f99ee087a0ccc4ba3be0 electrs_linux_v0.9.1.zip
6+
10f468e9e617bfe8f9f4897fa4cbbb92fe809d977b747e4326f4c8e5dc1b3a51 electrs_macos_v0.9.1.zip
7+
2b2f8aef35cd8e16e109b948a903d010aa472f6cdf2147d47e01fd95cd1785da electrs_linux_v0.9.11.zip
8+
b794287a5d98e590deadf07a3eb391cc1a53ef160c8cdcb8e6b14d856c7b181d electrs_macos_v0.9.11.zip
9+
448693f42fa2e310bd86ba9a7304c9ab464854a3c7e4c3eaa8c774efeb0fbdd1 electrs_linux_v0.10.6.zip
10+
016ad9ef227c12ae6096ada1db09179d3330a4062ba2ab65390d6988f659be05 electrs_macos_v0.10.6.zip

electrsd/src/error.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/// All the possible error in this crate
2+
#[derive(Debug)]
3+
pub enum Error {
4+
/// Wrapper of io Error
5+
Io(std::io::Error),
6+
7+
/// Wrapper of bitcoind Error
8+
Bitcoind(corepc_node::Error),
9+
10+
/// Wrapper of electrum_client Error
11+
ElectrumClient(electrum_client::Error),
12+
13+
/// Wrapper of nix Error
14+
#[cfg(not(target_os = "windows"))]
15+
Nix(nix::Error),
16+
17+
/// Wrapper of early exit status
18+
EarlyExit(std::process::ExitStatus),
19+
20+
/// Returned when both tmpdir and staticdir is specified in `Conf` options
21+
BothDirsSpecified,
22+
23+
/// Returned when calling methods requiring the bitcoind executable but none is found
24+
/// (no feature, no `ELECTRS_EXEC`, no `electrs` in `PATH` )
25+
NoElectrsExecutableFound,
26+
27+
/// Returned if both env vars `ELECTRS_EXEC` and `ELECTRS_EXE` are found
28+
BothEnvVars,
29+
}
30+
31+
impl std::error::Error for Error {
32+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33+
match self {
34+
Error::Io(e) => Some(e),
35+
Error::Bitcoind(e) => Some(e),
36+
Error::ElectrumClient(e) => Some(e),
37+
// Error::BitcoinCoreRpc(e) => Some(e),
38+
#[cfg(not(target_os = "windows"))]
39+
Error::Nix(e) => Some(e),
40+
41+
_ => None,
42+
}
43+
}
44+
}
45+
46+
impl std::fmt::Display for Error {
47+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48+
write!(f, "{:?}", self)
49+
}
50+
}
51+
52+
impl From<std::io::Error> for Error {
53+
fn from(e: std::io::Error) -> Self {
54+
Error::Io(e)
55+
}
56+
}
57+
58+
impl From<corepc_node::Error> for Error {
59+
fn from(e: corepc_node::Error) -> Self {
60+
Error::Bitcoind(e)
61+
}
62+
}
63+
64+
impl From<electrum_client::Error> for Error {
65+
fn from(e: electrum_client::Error) -> Self {
66+
Error::ElectrumClient(e)
67+
}
68+
}
69+
70+
#[cfg(not(target_os = "windows"))]
71+
impl From<nix::Error> for Error {
72+
fn from(e: nix::Error) -> Self {
73+
Error::Nix(e)
74+
}
75+
}

0 commit comments

Comments
 (0)