diff --git a/Cargo.lock b/Cargo.lock
index 70abbb5..7a37539 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -47,9 +47,9 @@ dependencies = [
[[package]]
name = "base64"
-version = "0.22.1"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bech32"
@@ -57,23 +57,6 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
-[[package]]
-name = "bindex"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "becb30e2688944707fe12c33fc1d6920fc4c83f67224cd81bf2767062b852f9e"
-dependencies = [
- "bitcoin",
- "bitcoin_slices",
- "log",
- "rayon",
- "rust-rocksdb",
- "serde",
- "serde_json",
- "thiserror 2.0.16",
- "ureq",
-]
-
[[package]]
name = "bindgen"
version = "0.69.5"
@@ -96,9 +79,9 @@ dependencies = [
[[package]]
name = "bitcoin"
-version = "0.32.9"
+version = "0.32.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3"
+checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66"
dependencies = [
"base58ck",
"bech32",
@@ -156,12 +139,37 @@ dependencies = [
[[package]]
name = "bitcoin_slices"
-version = "0.11.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b80fcc031ed36f91c31639a4d97acb1487985fdee34565651b2f52f4346859"
+checksum = "7943d4257fdfc85afe2a13d2b539a5213e97bb978329dd79b624acbce73042fb"
dependencies = [
"bitcoin",
"bitcoin_hashes",
+ "sha2",
+]
+
+[[package]]
+name = "bitcoincore-rpc"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee"
+dependencies = [
+ "bitcoincore-rpc-json",
+ "jsonrpc",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "bitcoincore-rpc-json"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a"
+dependencies = [
+ "bitcoin",
+ "serde",
+ "serde_json",
]
[[package]]
@@ -170,6 +178,15 @@ version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "block2"
version = "0.6.2"
@@ -179,12 +196,6 @@ dependencies = [
"objc2",
]
-[[package]]
-name = "bytes"
-version = "1.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
-
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
@@ -280,6 +291,15 @@ dependencies = [
"void",
]
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
@@ -315,16 +335,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
-name = "ctrlc"
-version = "3.5.2"
+name = "crypto-common"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "ctrlc"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790"
dependencies = [
"dispatch2",
"nix",
"windows-sys 0.61.0",
]
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
[[package]]
name = "dirs-next"
version = "2.0.0"
@@ -369,8 +409,10 @@ name = "electrs"
version = "0.11.1"
dependencies = [
"anyhow",
- "bindex",
+ "bitcoin",
"bitcoin-test-data",
+ "bitcoin_slices",
+ "bitcoincore-rpc",
"configure_me",
"configure_me_codegen",
"crossbeam-channel",
@@ -378,10 +420,11 @@ dependencies = [
"dirs-next",
"env_logger",
"hex_lit",
- "jsonrpc",
"log",
+ "parking_lot",
"prometheus",
"rayon",
+ "rust-rocksdb",
"serde",
"serde_derive",
"serde_json",
@@ -431,6 +474,16 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -487,22 +540,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
-[[package]]
-name = "http"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
-dependencies = [
- "bytes",
- "itoa",
-]
-
-[[package]]
-name = "httparse"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
-
[[package]]
name = "httpdate"
version = "1.0.3"
@@ -553,11 +590,12 @@ dependencies = [
[[package]]
name = "jsonrpc"
-version = "0.20.1"
+version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f106cca655869522988b976127096f0aa36e0f1a8ff1f1f425a5c85b61e59391"
+checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf"
dependencies = [
"base64",
+ "minreq",
"serde",
"serde_json",
]
@@ -576,9 +614,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.180"
+version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libredox"
@@ -625,9 +663,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.33"
+version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
@@ -642,10 +680,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
-name = "nix"
-version = "0.31.1"
+name = "minreq"
+version = "2.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
+checksum = "f0d2aaba477837b46ec1289588180fabfccf0c3b1d1a0c6b1866240cd6cd5ce9"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
@@ -722,18 +771,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bddc33f680b79eaf1e2e56da792c3c2236f86985bbc3a886e8ddee17ae4d3a4"
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -818,10 +870,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
-name = "rayon"
-version = "1.12.0"
+name = "rand"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
@@ -956,6 +1038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes",
+ "rand",
"secp256k1-sys",
"serde",
]
@@ -1001,9 +1084,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.150"
+version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
@@ -1012,6 +1095,17 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "shlex"
version = "1.3.0"
@@ -1020,9 +1114,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
-version = "0.4.4"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d"
+checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e"
dependencies = [
"libc",
"signal-hook-registry",
@@ -1056,9 +1150,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.25.0"
+version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.3",
@@ -1137,6 +1231,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
[[package]]
name = "unicode-ident"
version = "1.0.18"
@@ -1149,43 +1249,18 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
-[[package]]
-name = "ureq"
-version = "3.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
-dependencies = [
- "base64",
- "log",
- "percent-encoding",
- "ureq-proto",
- "utf-8",
-]
-
-[[package]]
-name = "ureq-proto"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
-dependencies = [
- "base64",
- "http",
- "httparse",
- "log",
-]
-
-[[package]]
-name = "utf-8"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
[[package]]
name = "void"
version = "1.0.2"
@@ -1335,6 +1410,26 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "zerocopy"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "zmij"
version = "1.0.2"
diff --git a/Cargo.toml b/Cargo.toml
index e19ad2e..8af0480 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,30 +23,46 @@ spec = "internal/config_specification.toml"
[dependencies]
anyhow = "1.0"
-bindex = { version = "0.1.2", default-features = false, features = ["bitcoin_serde"] }
+bitcoin = { version = "0.32.8", features = ["serde", "rand-std"] }
+bitcoin_slices = { version = "0.10.0", features = ["bitcoin", "sha2"] }
+bitcoincore-rpc = { version = "0.19.0" }
configure_me = "0.4"
crossbeam-channel = "0.5"
dirs-next = "2.0"
env_logger = "0.10"
-jsonrpc = { version = "0.20.1", default-features = false, features = ["simple_http"]}
log = "0.4"
+parking_lot = "0.12"
prometheus = { version = "0.14", optional = true }
-rayon = "1.12"
+rayon = "1.11"
serde = "1.0.184"
serde_derive = "1.0.184"
serde_json = "1.0"
tiny_http = { version = "0.12", optional = true }
[target.'cfg(windows)'.dependencies]
-ctrlc = "=3.5.2"
+ctrlc = "=3.5.1"
[target.'cfg(not(windows))'.dependencies]
signal-hook = "0.4"
+[dependencies.rust-rocksdb]
+version = "0.36"
+
+default-features = false
+# ZSTD is used for data compression
+# Snappy is only for checking old DB
+features = ["zstd", "snappy"]
+
[build-dependencies]
configure_me_codegen = { version = "0.4.8", default-features = false }
[dev-dependencies]
bitcoin-test-data = "0.2.0"
hex_lit = "0.1.1"
-tempfile = "3.25"
+tempfile = "3.24"
+
+[profile.release]
+lto = true
+
+[lints.clippy]
+uninlined_format_args = "allow" # TODO: https://github.com/romanz/electrs/issues/1199
diff --git a/Dockerfile.ci b/Dockerfile.ci
index eadb696..0231276 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -4,7 +4,7 @@
FROM debian:trixie-slim AS base
RUN apt-get update -qqy
-RUN apt-get install -qqy librocksdb-dev libsqlite3-dev libevent-dev libboost-dev wget
+RUN apt-get install -qqy librocksdb-dev wget
### Electrum Rust Server ###
FROM base AS electrs-build
@@ -17,15 +17,16 @@ ENV ROCKSDB_INCLUDE_DIR=/usr/include
ENV ROCKSDB_LIB_DIR=/usr/lib
RUN cargo install --locked --path .
-### Bitcoin Core (custom build for /blockpart/ REST endpoint) ###
+### Bitcoin Core ###
FROM base AS bitcoin-build
# Download
WORKDIR /build/bitcoin
-
-RUN wget -q https://bitcoincore.org/bin/bitcoin-core-31.1/bitcoin-31.1-x86_64-linux-gnu.tar.gz
-RUN tar xvf bitcoin-31.1-x86_64-linux-gnu.tar.gz
-RUN mv -v bitcoin-31.1/bin/bitcoind .
-RUN mv -v bitcoin-31.1/bin/bitcoin-cli .
+ARG ARCH=x86_64
+ARG BITCOIND_VERSION=29.0
+RUN wget -q https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/bitcoin-$BITCOIND_VERSION-$ARCH-linux-gnu.tar.gz
+RUN tar xvf bitcoin-$BITCOIND_VERSION-$ARCH-linux-gnu.tar.gz
+RUN mv -v bitcoin-$BITCOIND_VERSION/bin/bitcoind .
+RUN mv -v bitcoin-$BITCOIND_VERSION/bin/bitcoin-cli .
FROM base AS result
# Copy the binaries
diff --git a/doc/config.md b/doc/config.md
index b234e72..3784b20 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -5,12 +5,11 @@ If you use automated systems, refer to their documentation first!
### Bitcoind configuration
-REST API must be **enabled** for block indexing and transaction lookup.
-
-Pruning must be **disabled** for `electrs` to work.
-
+Pruning must be turned **off** for `electrs` to work.
`txindex` is allowed but unnecessary for `electrs`.
However, you might still need it if you run other services (e.g.`eclair`).
+The option `maxconnections` (if used) should be set to 12 or more for bitcoind to accept inbound p2p connections.
+Note that setting `maxuploadtarget` may cause p2p-based sync to fail - so consider using `-whitelist=download@127.0.0.1` to disable the limit for local p2p connections.
The highly recommended way of authenticating `electrs` is using cookie file.
It's the most [secure](https://github.com/Kixunil/security_writings/blob/master/cookie_files.md) and robust method.
@@ -22,7 +21,7 @@ You can skip it if you're running both daemons under the same user and with the
Example command for running `bitcoind` (assuming same user, default dirs):
```bash
-$ bitcoind -server=1 -rest=1 -txindex=0 -prune=0
+$ bitcoind -server=1 -txindex=0 -prune=0
```
### Electrs configuration
@@ -140,7 +139,7 @@ HiddenServiceVersion 3
HiddenServicePort 50001 127.0.0.1:50001
```
-If you use [the *beta* Debian repository](binaries.md#native-os-packages),
+If you use [the *beta* Debian repository](binaries.md#cnative-os-packages),
it is cleaner to install `tor-hs-patch-config` using `apt` and then placing the configuration into a file inside `/etc/tor/hidden-services.d`.
Restart the service:
@@ -163,7 +162,7 @@ For more details, see http://docs.electrum.org/en/latest/tor.html.
### Sample Systemd Unit File
-If you use [the *beta* Debian repository](binaries.md#native-os-packages), you should skip this section,
+If you use [the *beta* Debian repository](binaries.md#cnative-os-packages), you should skip this section,
as the appropriate systemd unit file is installed automatically.
You may wish to have systemd manage electrs so that it's "always on".
diff --git a/doc/config_example.toml b/doc/config_example.toml
index 59cf429..1272ddb 100644
--- a/doc/config_example.toml
+++ b/doc/config_example.toml
@@ -14,6 +14,9 @@ cookie_file = "/var/run/bitcoin-mainnet/cookie"
# The listening RPC address of bitcoind, port is usually 8332
daemon_rpc_addr = "127.0.0.1:8332"
+# The listening P2P address of bitcoind, port is usually 8333
+daemon_p2p_addr = "127.0.0.1:8333"
+
# Directory where the index should be stored. It should have at least 70GB of free space.
db_dir = "/some/fast/storage/with/big/size"
diff --git a/doc/install.md b/doc/install.md
index 3ac8229..392cdf5 100644
--- a/doc/install.md
+++ b/doc/install.md
@@ -1,14 +1,21 @@
## Quickstart
-Building from source on an Ubuntu 25.04 / Debian 13:
+
+Building from source on an Ubuntu 25.04 / Debian 13:
```bash
$ sudo apt update
$ sudo apt install -y build-essential libclang-dev git cargo
-$ git clone https://github.com/romanz/electrs && cd electrs
-$ cargo run --locked --release -- --version # build and print the latest version
+$ git clone https://github.com/romanz/electrs
+$ cd electrs
+$ cargo build --locked --release
+$ ./target/release/electrs --version # should print the latest version
```
+
+
+[](https://asciinema.org/a/XKznxilP4O7lCZiVZ9vZNd5vx?speed=3)
+
## Manual installation from source
**See below for automated/binary installation options.**
@@ -19,7 +26,7 @@ Note for Raspberry Pi 4 owners: the old versions of OS/toolchains produce broken
Make sure to use latest OS! (see #226)
Install [recent Rust](https://rustup.rs/) (1.85.0+, `apt install cargo` is preferred for Debian 13),
-[latest Bitcoin Core](https://bitcoincore.org/en/download/) (31.0+)
+[latest Bitcoin Core](https://bitcoincore.org/en/download/) (0.21+)
and [latest Electrum wallet](https://electrum.org/#download) (4.0+).
Also, install the following packages (on Debian or Ubuntu):
diff --git a/doc/schema.md b/doc/schema.md
index 901b775..ab8af29 100644
--- a/doc/schema.md
+++ b/doc/schema.md
@@ -1 +1,53 @@
-See https://github.com/romanz/bindex-rs for more details.
+# Index Schema
+
+The index is stored at a single RocksDB database using the following column families.
+Most of the data is stored in key-only DB rows (i.e. having empty values).
+
+## Transaction outputs' index (`funding`)
+
+Allows efficiently finding all funding transactions for a specific address:
+
+| Script Hash Prefix | Confirmed Block Height |
+| -------------------- | ---------------------- |
+| `SHA256(script)[:8]` | `height as u32` |
+
+## Transaction inputs' index (`spending`)
+
+Allows efficiently finding spending transaction of a specific output:
+
+| Previous Outpoint Prefix | Confirmed Block Height |
+| ------------------------ | ---------------------- |
+| `txid[:8] as u64 + vout` | `height as u32` |
+
+
+## Transaction ID index (`txid`)
+
+In order to save storage space, we map the 8-byte transaction ID prefix to its confirmed block height:
+
+| Txid Prefix | Confirmed height |
+| ----------- | ---------------- |
+| `txid[:8]` | `height as u32` |
+
+Note that this mapping allows us to use `getrawtransaction` RPC to retrieve actual transaction data from without `-txindex` enabled
+(by explicitly specifying the [blockhash](https://github.com/bitcoin/bitcoin/commit/497d0e014cc79d46531d570e74e4aeae72db602d)).
+
+## Headers (`headers`)
+
+For faster loading, we store all block headers in RocksDB:
+
+| Serialized header |
+| ----------------------- |
+| `header as BlockHeader` |
+
+In addition, we also store the chain tip:
+
+| Key | | Value |
+| --- | - | ------------------------ |
+| `T` | | `blockhash as BlockHash` |
+
+## Configuration (`config`)
+
+| Key | | Value |
+| --- | - | --------------------------- |
+| `C` | | `serialized config as JSON` |
+
diff --git a/doc/upgrading.md b/doc/upgrading.md
index fd060da..a52bffd 100644
--- a/doc/upgrading.md
+++ b/doc/upgrading.md
@@ -1,20 +1,3 @@
-### Important changes from versions older than 0.12.0
-
-In 0.12.0 we have changed the RocksDB index format to optimize electrs performance.
-
-We also use new bitcoind REST API endpoints (instead of P2P protocol)
-
-* [#32540 (index: fetch spent transaction outputs by blockhash)](https://github.com/bitcoin/bitcoin/pull/32540) → released in 30.0
-* [#33657 (rest: allow reading partial block data from storage)](https://github.com/bitcoin/bitcoin/pull/33657) → released in 31.0
-
-Upgrading checklist:
-
-* Make sure you upgrade at a time when you don't need to use electrs for a while.
- Because of reindex electrs will be unable to serve your requests for a few hours.
- (The exact time depends on your hardware.)
-* Make sure you have at least 120 GB of free space (for reindexing mainnet chain).
-
-
### Important changes from versions older than 0.9.3
* If you use `verbose` (or `-v` argument), switch to `log_filters` (or `RUST_LOG` environment variable).
diff --git a/doc/usage.md b/doc/usage.md
index 8ec9ee9..e6da8c7 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -1,63 +1,77 @@
## Quickstart
-Assuming Bitcoin Core 31+ is installed on the same machine (with the standard configuration at `~/.bitcoin/bitcoin.conf`):
+
+Assuming Bitcoin Core 0.21+ is installed on the same machine (with the standard configuration at `~/.bitcoin/bitcoin.conf`):
```bash
-$ bitcoind -server=1 -rest=1 -prune=0 &
+$ bitcoind -server=1 -prune=0 &
$ # ... wait until the chain is synced (e.g. using `bitcoin-cli getblockchaininfo`)
-$ RUST_LOG=INFO electrs --network bitcoin --db-dir ./db --daemon-dir ~/.bitcoin
+$ electrs --log-filters=INFO --db-dir ./db --daemon-dir ~/.bitcoin --network bitcoin
```
+
+
+[](https://asciinema.org/a/zRNZp5HsBDi5rAlGWU7470Pzl?speed=3)
+
## Usage
-First index sync should take ~2 hours for ~800GB `.bitcoin/blocks/` @ July 2026 (on a 6-core CPU, 32 GB RAM, 2TB NVMe):
+First index sync should take ~6.5 hours for ~504GB @ August 2023 (on a dual core Intel CPU @ 3.3 GHz, 8 GB RAM, 1TB WD Blue HDD):
```bash
-$ du -ch ~/.bitcoin/blocks/*.dat | tail -n1
-802G total
+$ du -ch ~/.bitcoin/blocks/blk*.dat | tail -n1
+336G total
-$ RUST_LOG=INFO electrs --network bitcoin --db-dir ./db --daemon-dir ~/.bitcoin
-Starting electrs 0.12.0 on x86_64 linux with Config { network: Bitcoin, db_dir: "./db", ... }
-[2026-07-18T16:37:24.024Z INFO electrs::metrics::metrics_impl] serving Prometheus metrics on 127.0.0.1:4224
-[2026-07-18T16:37:24.025Z INFO electrs::server] serving Electrum RPC on 127.0.0.1:50001
-[2026-07-18T16:37:24.025Z INFO bindex::chain] index: Config { db_path: "./db/bitcoin", url: "http://127.0.0.1:8332" }
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF config: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF headers: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF txpos: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF txid: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF script_hash: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF spending: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.054Z INFO bindex::db] CF funding: 0 files, 0.000000 MBs
-[2026-07-18T16:37:24.129Z INFO bindex::chain] block=00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09 height=1000: indexed 1001 blocks, 0.226[MB], dt = 0.047[s]: 0.047 [ms/block], 0.000 [MB/block], 4.837 [MB/s]
-[2026-07-18T16:37:24.196Z INFO bindex::chain] block=00000000dfd5d65c9d8561b4b8f60a63018fe3933ecb131fb37f905f87da951a height=2000: indexed 1000 blocks, 0.234[MB], dt = 0.043[s]: 0.043 [ms/block], 0.000 [MB/block], 5.376 [MB/s]
-[2026-07-18T16:37:24.263Z INFO bindex::chain] block=000000004a81b9aa469b11649996ecb0a452c16d1181e72f9f980850a1c5ecce height=3000: indexed 1000 blocks, 0.230[MB], dt = 0.043[s]: 0.043 [ms/block], 0.000 [MB/block], 5.298 [MB/s]
-[2026-07-18T16:37:24.331Z INFO bindex::chain] block=00000000922e2aa9e84a474350a3555f49f06061fd49df50a9352f156692a842 height=4000: indexed 1000 blocks, 0.234[MB], dt = 0.044[s]: 0.044 [ms/block], 0.000 [MB/block], 5.366 [MB/s]
-[2026-07-18T16:37:24.398Z INFO bindex::chain] block=000000004d78d2a8a93a1d20a24d721268690bebd2b51f7e80657d57e226eef9 height=5000: indexed 1000 blocks, 0.229[MB], dt = 0.043[s]: 0.043 [ms/block], 0.000 [MB/block], 5.349 [MB/s]
-...
-[2026-07-18T18:02:21.114Z INFO bindex::chain] block=0000000000000000000055222909c19ed96cd371013861337214a3c39a63d828 height=955000: indexed 1000 blocks, 1842.985[MB], dt = 9.457[s]: 9.457 [ms/block], 1.843 [MB/block], 194.886 [MB/s]
-[2026-07-18T18:02:33.592Z INFO bindex::chain] block=000000000000000000012d9ea47c8b3282ae1a7792d54b8b4bcc0536c1883191 height=956000: indexed 1000 blocks, 1846.173[MB], dt = 9.522[s]: 9.522 [ms/block], 1.846 [MB/block], 193.879 [MB/s]
-[2026-07-18T18:02:45.872Z INFO bindex::chain] block=00000000000000000000e898fc9d01b0729d8830f2068248debbff575a3b0990 height=957000: indexed 1000 blocks, 1855.303[MB], dt = 9.295[s]: 9.295 [ms/block], 1.855 [MB/block], 199.600 [MB/s]
-[2026-07-18T18:02:58.221Z INFO bindex::chain] block=000000000000000000022c6a6d538a4372b0eeadb4ea9eb2edd4e32ec780084b height=958000: indexed 1000 blocks, 1844.747[MB], dt = 9.376[s]: 9.376 [ms/block], 1.845 [MB/block], 196.759 [MB/s]
-[2026-07-18T18:03:05.539Z INFO bindex::chain] block=000000000000000000020934afa0945d959250e9a4598d39c53fa280639b096a height=958594: indexed 594 blocks, 1115.055[MB], dt = 5.357[s]: 9.018 [ms/block], 1.877 [MB/block], 208.155 [MB/s]
-[2026-07-18T18:03:05.666Z INFO bindex::db] started auto compactions
+$ ./target/release/electrs --network bitcoin --db-dir ./db --daemon-dir /home/user/.bitcoin
+Starting electrs 0.10.0 on x86_64 linux with Config { network: Bitcoin, db_path: "./db/bitcoin", daemon_dir: "/home/user/.bitcoin", daemon_auth: CookieFile("/home/user/.bitcoin/.cookie"), daemon_rpc_addr: 127.0.0.1:8332, daemon_p2p_addr: 127.0.0.1:8333, electrum_rpc_addr: 127.0.0.1:50001, monitoring_addr: 127.0.0.1:4224, wait_duration: 10s, jsonrpc_timeout: 15s, index_batch_size: 10, index_lookup_limit: None, reindex_last_blocks: 0, auto_reindex: true, ignore_mempool: false, sync_once: false, skip_block_download_wait: false, disable_electrum_rpc: false, server_banner: "Welcome to electrs 0.10.0 (Electrum Rust Server)!", magic: f9beb4d9, args: [] }
+[2023-08-16T19:17:11.193Z INFO electrs::metrics::metrics_impl] serving Prometheus metrics on 127.0.0.1:4224
+[2023-08-16T19:17:11.193Z INFO electrs::server] serving Electrum RPC on 127.0.0.1:50001
+[2023-08-16T19:17:12.355Z INFO electrs::db] "./db/bitcoin": 0 SST files, 0 GB, 0 Grows
+[2023-08-16T19:17:12.446Z INFO electrs::index] indexing 2000 blocks: [1..2000]
+[2023-08-16T19:17:12.866Z INFO electrs::chain] chain updated: tip=00000000dfd5d65c9d8561b4b8f60a63018fe3933ecb131fb37f905f87da951a, height=2000
+[2023-08-16T19:17:12.879Z INFO electrs::index] indexing 2000 blocks: [2001..4000]
+[2023-08-16T19:17:13.227Z INFO electrs::chain] chain updated: tip=00000000922e2aa9e84a474350a3555f49f06061fd49df50a9352f156692a842, height=4000
+[2023-08-16T19:17:13.238Z INFO electrs::index] indexing 2000 blocks: [4001..6000]
+[2023-08-16T19:17:13.587Z INFO electrs::chain] chain updated: tip=00000000dbbb79792303bdd1c6c4d7ab9c21bba0667213c2eca955e11230c5a5, height=6000
+[2023-08-16T19:17:13.598Z INFO electrs::index] indexing 2000 blocks: [6001..8000]
+[2023-08-16T19:17:13.950Z INFO electrs::chain] chain updated: tip=0000000094fbacdffec05aea9847000522a258c269ae37a74a818afb96fc27d9, height=8000
+[2023-08-16T19:17:13.961Z INFO electrs::index] indexing 2000 blocks: [8001..10000]
+<...>
+[2023-08-17T00:13:16.443Z INFO electrs::index] indexing 2000 blocks: [798001..800000]
+[2023-08-17T00:14:58.310Z INFO electrs::chain] chain updated: tip=00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054, height=800000
+[2023-08-17T00:14:58.325Z INFO electrs::index] indexing 2000 blocks: [800001..802000]
+[2023-08-17T00:16:36.425Z INFO electrs::chain] chain updated: tip=0000000000000000000311b41f1d611f977b024b947568c1dd760704360f148a, height=802000
+[2023-08-17T00:16:36.437Z INFO electrs::index] indexing 1534 blocks: [802001..803534]
+[2023-08-17T00:17:51.338Z INFO electrs::chain] chain updated: tip=00000000000000000003c0cd1b62ed8bb502e24bcbfeee16e81d6ea33d026263, height=803534
+[2023-08-17T00:18:00.592Z INFO electrs::db] starting config compaction
+[2023-08-17T00:18:00.778Z INFO electrs::db] starting headers compaction
+[2023-08-17T00:18:00.870Z INFO electrs::db] starting txid compaction
+[2023-08-17T00:33:34.370Z INFO electrs::db] starting funding compaction
+[2023-08-17T01:03:56.784Z INFO electrs::db] starting spending compaction
+[2023-08-17T01:35:05.983Z INFO electrs::db] finished full compaction
+[2023-08-17T01:36:23.300Z INFO electrs::index] indexing 5 blocks: [803535..803539]
+[2023-08-17T01:36:23.646Z INFO electrs::chain] chain updated: tip=000000000000000000006a3aaddd4b643607b33e000f1200d35005c330ecfa88, height=803539
+[2023-08-17T01:41:26.009Z INFO electrs::index] indexing 1 blocks: [803540..803540]
+[2023-08-17T01:41:26.143Z INFO electrs::chain] chain updated: tip=00000000000000000003266d31db92629b64241eef7ce708244f6d6283b080b4, height=803540
+[2023-08-17T01:42:42.999Z INFO electrs::index] indexing 1 blocks: [803541..803541]
+[2023-08-17T01:42:43.153Z INFO electrs::chain] chain updated: tip=00000000000000000000884a77c8b8ad2fb0c25510a3251bf5ef57f0db275146, height=803541
```
You can specify options via command-line parameters, environment variables or using config files.
See the documentation above.
-Note that the final DB size should be ~7% of the `blocks/*.dat` files, but it may increase to ~14% at the end of the initial sync (just before the [full compaction is invoked](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)).
+Note that the final DB size should be ~10% of the `blk*.dat` files, but it may increase to ~20% at the end of the initial sync (just before the [full compaction is invoked](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)).
It should take roughly 18 hours to sync and compact the index on an ODROID-HC1 with 8 CPU cores @ 2GHz, 2GB RAM, and an SSD using the command above.
The index database is stored here:
```bash
-$ du -h db/bitcoin/
-56G db/bitcoin/
+$ du db/
+42G db/mainnet/
```
See [extra configuration suggestions](config.md#extra-configuration-suggestions) that you might want to consider.
## Electrum client
-If you happen to use the Electrum client from [the *beta* Debian repository](binaries.md#native-os-packages), it's pre-configured out-of-the-box already
+If you happen to use the Electrum client from [the *beta* Debian repository](binaries.md#cnative-os-packages), it's pre-configured out-of-the-box already
Read below otherwise.
There's a prepared script for launching `electrum` in such way to connect only to the local `electrs` instance to protect your privacy.
@@ -84,7 +98,7 @@ You can invoke any supported RPC using `netcat`, for example:
```
$ echo '{"jsonrpc": "2.0", "method": "server.version", "params": ["", "1.4"], "id": 0}' | netcat 127.0.0.1 50001
-{"id":0,"jsonrpc":"2.0","result":["electrs 0.12.0","1.4"]}
+{"id":0,"jsonrpc":"2.0","result":["electrs 0.9.0","1.4"]}
```
For more complex tasks, you may need to convert addresses to
diff --git a/examples/tx_collisions.rs b/examples/tx_collisions.rs
new file mode 100644
index 0000000..0b7fd58
--- /dev/null
+++ b/examples/tx_collisions.rs
@@ -0,0 +1,31 @@
+use anyhow::{Context, Result};
+use rust_rocksdb::{ColumnFamilyDescriptor, IteratorMode, Options, DB};
+
+fn main() -> Result<()> {
+ let path = std::env::args().nth(1).context("missing DB path")?;
+ let cf_names = DB::list_cf(&Options::default(), &path)?;
+ let cfs: Vec<_> = cf_names
+ .iter()
+ .map(|name| ColumnFamilyDescriptor::new(name, Options::default()))
+ .collect();
+ let db = DB::open_cf_descriptors(&Options::default(), &path, cfs)?;
+ let cf = db.cf_handle("txid").context("missing column family")?;
+
+ let mut state: Option<(u64, u32)> = None;
+ for row in db.iterator_cf(cf, IteratorMode::Start) {
+ let (curr, _value) = row?;
+ let curr_prefix = u64::from_le_bytes(curr[..8].try_into()?);
+ let curr_height = u32::from_le_bytes(curr[8..].try_into()?);
+
+ if let Some((prev_prefix, prev_height)) = state {
+ if prev_prefix == curr_prefix {
+ eprintln!(
+ "prefix={:x} heights: {} {}",
+ curr_prefix, prev_height, curr_height
+ );
+ };
+ }
+ state = Some((curr_prefix, curr_height));
+ }
+ Ok(())
+}
diff --git a/internal/config_specification.toml b/internal/config_specification.toml
index f445f41..f4ae65e 100644
--- a/internal/config_specification.toml
+++ b/internal/config_specification.toml
@@ -63,7 +63,7 @@ doc = "JSONRPC authentication cookie file (default: ~/.bitcoin/.cookie)"
[[param]]
name = "network"
type = "crate::config::BitcoinNetwork"
-convert_into = "crate::bitcoin::Network"
+convert_into = "::bitcoin::Network"
doc = "Select Bitcoin network type ('bitcoin', 'testnet', 'testnet4', 'regtest' or 'signet')"
default = "Default::default()"
@@ -76,6 +76,10 @@ doc = "Electrum server JSONRPC 'addr:port' to listen on (default: '127.0.0.1:500
name = "daemon_rpc_addr"
type = "crate::config::ResolvAddr"
doc = "Bitcoin daemon JSONRPC 'addr:port' to connect (default: 127.0.0.1:8332 for mainnet, 127.0.0.1:18332 for testnet, 127.0.0.1:18443 for regtest and 127.0.0.1:18554 for signet)"
+[[param]]
+name = "daemon_p2p_addr"
+type = "crate::config::ResolvAddr"
+doc = "Bitcoin daemon p2p 'addr:port' to connect (default: 127.0.0.1:8333 for mainnet, 127.0.0.1:18333 for testnet, 127.0.0.1:18444 for regtest and 127.0.0.1:38333 for signet)"
[[param]]
name = "monitoring_addr"
@@ -94,6 +98,12 @@ type = "u64"
doc = "Duration to wait until bitcoind JSON-RPC timeouts (must be greater than wait_duration_secs)."
default = "15"
+[[param]]
+name = "index_batch_size"
+type = "usize"
+doc = "Number of blocks to get in a single p2p protocol request from bitcoind"
+default = "10"
+
[[switch]]
name = "ignore_mempool"
doc = "Don't sync mempool - queries will show only confirmed transactions."
@@ -136,3 +146,8 @@ default = "concat!(\"Welcome to electrs \", env!(\"CARGO_PKG_VERSION\"), \" (Ele
name = "log_filters"
type = "String"
doc = "Logging filters, overriding `RUST_LOG` environment variable (see https://docs.rs/env_logger/ for details)"
+
+[[param]]
+name = "magic"
+type = "String"
+doc = "network magic for custom network in hex format, as found in Bitcoin Core logs"
diff --git a/server.sh b/server.sh
index 7a7fe5f..c66a4c9 100755
--- a/server.sh
+++ b/server.sh
@@ -9,7 +9,7 @@ NETWORK=$1
shift
DB=${DB-./db}
-export RUST_LOG=${RUST_LOG-INFO}
+export RUST_LOG=${RUST_LOG-electrs=INFO}
target/release/electrs --network $NETWORK --db-dir $DB --daemon-dir $HOME/.bitcoin $*
# use SIGINT to quit
diff --git a/src/cache.rs b/src/cache.rs
new file mode 100644
index 0000000..b09a916
--- /dev/null
+++ b/src/cache.rs
@@ -0,0 +1,43 @@
+use bitcoin::Txid;
+use parking_lot::RwLock;
+
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use crate::metrics::{self, Histogram, Metrics};
+
+pub(crate) struct Cache {
+ txs: Arc>>>,
+
+ // stats
+ txs_size: Histogram,
+}
+
+impl Cache {
+ pub fn new(metrics: &Metrics) -> Self {
+ Cache {
+ txs: Default::default(),
+ txs_size: metrics.histogram_vec(
+ "cache_txs_size",
+ "Cached transactions' size (in bytes)",
+ "type",
+ metrics::default_size_buckets(),
+ ),
+ }
+ }
+
+ pub fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Box<[u8]>) {
+ self.txs.write().entry(txid).or_insert_with(|| {
+ let tx = f();
+ self.txs_size.observe("serialized", tx.len() as f64);
+ tx
+ });
+ }
+
+ pub fn get_tx(&self, txid: &Txid, f: F) -> Option
+ where
+ F: FnOnce(&[u8]) -> T,
+ {
+ self.txs.read().get(txid).map(|tx_bytes| f(tx_bytes))
+ }
+}
diff --git a/src/chain.rs b/src/chain.rs
new file mode 100644
index 0000000..a55e50d
--- /dev/null
+++ b/src/chain.rs
@@ -0,0 +1,261 @@
+use std::collections::HashMap;
+
+use bitcoin::blockdata::block::Header as BlockHeader;
+use bitcoin::{BlockHash, Network};
+
+/// A new header found, to be added to the chain at specific height
+pub(crate) struct NewHeader {
+ header: BlockHeader,
+ hash: BlockHash,
+ height: usize,
+}
+
+impl NewHeader {
+ pub(crate) fn from((header, height): (BlockHeader, usize)) -> Self {
+ Self {
+ header,
+ hash: header.block_hash(),
+ height,
+ }
+ }
+
+ pub(crate) fn height(&self) -> usize {
+ self.height
+ }
+
+ pub(crate) fn hash(&self) -> BlockHash {
+ self.hash
+ }
+}
+
+/// Current blockchain headers' list
+pub struct Chain {
+ headers: Vec<(BlockHash, BlockHeader)>,
+ heights: HashMap,
+}
+
+impl Chain {
+ // create an empty chain
+ pub fn new(network: Network) -> Self {
+ let genesis = bitcoin::blockdata::constants::genesis_block(network);
+ let genesis_hash = genesis.block_hash();
+ Self {
+ headers: vec![(genesis_hash, genesis.header)],
+ heights: std::iter::once((genesis_hash, 0)).collect(), // genesis header @ zero height
+ }
+ }
+
+ pub(crate) fn drop_last_headers(&mut self, n: usize) {
+ if n == 0 {
+ return;
+ }
+ let new_height = self.height().saturating_sub(n);
+ self.update(vec![NewHeader::from((
+ self.headers[new_height].1,
+ new_height,
+ ))]);
+ }
+
+ /// Load the chain from a collection of headers, up to the given tip
+ pub(crate) fn load(&mut self, headers: impl Iterator- , tip: BlockHash) {
+ let genesis_hash = self.headers[0].0;
+
+ let header_map: HashMap =
+ headers.map(|h| (h.block_hash(), h)).collect();
+ let mut blockhash = tip;
+ let mut new_headers: Vec<&BlockHeader> = Vec::with_capacity(header_map.len());
+ while blockhash != genesis_hash {
+ let header = match header_map.get(&blockhash) {
+ Some(header) => header,
+ None => panic!("missing header {} while loading from DB", blockhash),
+ };
+ blockhash = header.prev_blockhash;
+ new_headers.push(header);
+ }
+ info!("loading {} headers, tip={}", new_headers.len(), tip);
+ let new_headers = new_headers.into_iter().rev().copied(); // order by height
+ self.update(new_headers.zip(1..).map(NewHeader::from).collect())
+ }
+
+ /// Get the block hash at specified height (if exists)
+ pub(crate) fn get_block_hash(&self, height: usize) -> Option {
+ self.headers.get(height).map(|(hash, _header)| *hash)
+ }
+
+ /// Get the block header at specified height (if exists)
+ pub(crate) fn get_block_header(&self, height: usize) -> Option<&BlockHeader> {
+ self.headers.get(height).map(|(_hash, header)| header)
+ }
+
+ /// Get the block height given the specified hash (if exists)
+ pub(crate) fn get_block_height(&self, blockhash: &BlockHash) -> Option {
+ self.heights.get(blockhash).copied()
+ }
+
+ /// Update the chain with a list of new headers (possibly a reorg)
+ pub(crate) fn update(&mut self, headers: Vec) {
+ if let Some(first_height) = headers.first().map(|h| h.height) {
+ for (hash, _header) in self.headers.drain(first_height..) {
+ assert!(self.heights.remove(&hash).is_some());
+ }
+ for (h, height) in headers.into_iter().zip(first_height..) {
+ assert_eq!(h.height, height);
+ assert_eq!(h.hash, h.header.block_hash());
+ assert!(self.heights.insert(h.hash, h.height).is_none());
+ self.headers.push((h.hash, h.header));
+ }
+ info!(
+ "chain updated: tip={}, height={}",
+ self.headers.last().unwrap().0,
+ self.headers.len() - 1
+ );
+ }
+ }
+
+ /// Best block hash
+ pub(crate) fn tip(&self) -> BlockHash {
+ self.headers.last().expect("empty chain").0
+ }
+
+ /// Number of blocks (excluding genesis block)
+ pub(crate) fn height(&self) -> usize {
+ self.headers.len() - 1
+ }
+
+ /// List of block hashes for efficient fork detection and block/header sync
+ /// see https://en.bitcoin.it/wiki/Protocol_documentation#getblocks
+ pub(crate) fn locator(&self) -> Vec {
+ let mut result = vec![];
+ let mut index = self.headers.len() - 1;
+ let mut step = 1;
+ loop {
+ if result.len() >= 10 {
+ step *= 2;
+ }
+ result.push(self.headers[index].0);
+ if index == 0 {
+ break;
+ }
+ index = index.saturating_sub(step);
+ }
+ result
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Chain, NewHeader};
+ use bitcoin::blockdata::block::Header as BlockHeader;
+ use bitcoin::consensus::deserialize;
+ use bitcoin::Network::Regtest;
+ use hex_lit::hex;
+
+ #[test]
+ fn test_genesis() {
+ let regtest = Chain::new(Regtest);
+ assert_eq!(regtest.height(), 0);
+ assert_eq!(
+ regtest.tip(),
+ "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
+ .parse()
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn test_updates() {
+ let byte_headers = [
+hex!("0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1d14d3c7ff12d6adf494ebbcfba69baa915a066358b68a2b8c37126f74de396b1d61cc60ffff7f2000000000"),
+hex!("00000020d700ae5d3c705702e0a5d9ababd22ded079f8a63b880b1866321d6bfcb028c3fc816efcf0e84ccafa1dda26be337f58d41b438170c357cda33a68af5550590bc1e61cc60ffff7f2004000000"),
+hex!("00000020d13731bc59bc0989e06a5e7cab9843a4e17ad65c7ca47cd77f50dfd24f1f55793f7f342526aca9adb6ce8f33d8a07662c97d29d83b9e18117fb3eceecb2ab99b1e61cc60ffff7f2001000000"),
+hex!("00000020a603def3e1255cadfb6df072946327c58b344f9bfb133e8e3e280d1c2d55b31c731a68f70219472864a7cb010cd53dc7e0f67e57f7d08b97e5e092b0c3942ad51f61cc60ffff7f2001000000"),
+hex!("0000002041dd202b3b2edcdd3c8582117376347d48ff79ff97c95e5ac814820462012e785142dc360975b982ca43eecd14b4ba6f019041819d4fc5936255d7a2c45a96651f61cc60ffff7f2000000000"),
+hex!("0000002072e297a2d6b633c44f3c9b1a340d06f3ce4e6bcd79ebd4c4ff1c249a77e1e37c59c7be1ca0964452e1735c0d2740f0d98a11445a6140c36b55770b5c0bcf801f1f61cc60ffff7f2000000000"),
+hex!("000000200c9eb5889a8e924d1c4e8e79a716514579e41114ef37d72295df8869d6718e4ac5840f28de43ff25c7b9200aaf7873b20587c92827eaa61943484ca828bdd2e11f61cc60ffff7f2000000000"),
+hex!("000000205873f322b333933e656b07881bb399dae61a6c0fa74188b5fb0e3dd71c9e2442f9e2f433f54466900407cf6a9f676913dd54aad977f7b05afcd6dcd81e98ee752061cc60ffff7f2004000000"),
+hex!("00000020fd1120713506267f1dba2e1856ca1d4490077d261cde8d3e182677880df0d856bf94cfa5e189c85462813751ab4059643759ed319a81e0617113758f8adf67bc2061cc60ffff7f2000000000"),
+hex!("000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a2061cc60ffff7f2002000000"),
+ ];
+ let headers: Vec = byte_headers
+ .iter()
+ .map(|byte_header| deserialize(byte_header).unwrap())
+ .collect();
+
+ for chunk_size in 1..headers.len() {
+ let mut regtest = Chain::new(Regtest);
+ let mut height = 0;
+ let mut tip = regtest.tip();
+ for chunk in headers.chunks(chunk_size) {
+ let mut update = vec![];
+ for header in chunk {
+ height += 1;
+ tip = header.block_hash();
+ update.push(NewHeader::from((*header, height)))
+ }
+ regtest.update(update);
+ assert_eq!(regtest.tip(), tip);
+ assert_eq!(regtest.height(), height);
+ }
+ assert_eq!(regtest.tip(), headers.last().unwrap().block_hash());
+ assert_eq!(regtest.height(), headers.len());
+ }
+
+ // test loading from a list of headers and tip
+ let mut regtest = Chain::new(Regtest);
+ regtest.load(
+ headers.iter().copied(),
+ headers.last().unwrap().block_hash(),
+ );
+ assert_eq!(regtest.height(), headers.len());
+
+ // test getters
+ for (header, height) in headers.iter().zip(1usize..) {
+ assert_eq!(regtest.get_block_header(height), Some(header));
+ assert_eq!(regtest.get_block_hash(height), Some(header.block_hash()));
+ assert_eq!(regtest.get_block_height(&header.block_hash()), Some(height));
+ }
+
+ // test chain shortening
+ for i in (0..=headers.len()).rev() {
+ let hash = regtest.get_block_hash(i).unwrap();
+ assert_eq!(regtest.get_block_height(&hash), Some(i));
+ assert_eq!(regtest.height(), i);
+ assert_eq!(regtest.tip(), hash);
+ regtest.drop_last_headers(1);
+ }
+ assert_eq!(regtest.height(), 0);
+ assert_eq!(
+ regtest.tip(),
+ "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
+ .parse()
+ .unwrap()
+ );
+
+ regtest.drop_last_headers(1);
+ assert_eq!(regtest.height(), 0);
+ assert_eq!(
+ regtest.tip(),
+ "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
+ .parse()
+ .unwrap()
+ );
+
+ // test reorg
+ let mut regtest = Chain::new(Regtest);
+ regtest.load(
+ headers.iter().copied(),
+ headers.last().unwrap().block_hash(),
+ );
+ let height = regtest.height();
+
+ let new_header: BlockHeader = deserialize(&hex!("000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a7a6bcc60ffff7f2000000000")).unwrap();
+ regtest.update(vec![NewHeader::from((new_header, height))]);
+ assert_eq!(regtest.height(), height);
+ assert_eq!(
+ regtest.tip(),
+ "0e16637fe0700a7c52e9a6eaa58bd6ac7202652103be8f778680c66f51ad2e9b"
+ .parse()
+ .unwrap()
+ );
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index d147408..b844dda 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,5 +1,6 @@
-use crate::bitcoin::Network;
-use crate::types::Auth;
+use bitcoin::p2p::Magic;
+use bitcoin::Network;
+use bitcoincore_rpc::Auth;
use dirs_next::home_dir;
use std::ffi::{OsStr, OsString};
@@ -16,7 +17,7 @@ pub const ELECTRS_VERSION: &str = env!("CARGO_PKG_VERSION");
const DEFAULT_SERVER_ADDRESS: [u8; 4] = [127, 0, 0, 1]; // by default, serve on IPv4 localhost
mod internal {
- #![allow(unused_attributes, unused_imports, clippy::enum_variant_names)]
+ #![allow(unused_attributes, unused_imports)]
include!(concat!(env!("OUT_DIR"), "/configure_me_config.rs"));
}
@@ -121,20 +122,21 @@ impl From for Network {
}
/// Parsed and post-processed configuration
-#[allow(dead_code)]
#[derive(Debug)]
pub struct Config {
// See below for the documentation of each field:
pub network: Network,
- pub db_dir: PathBuf,
+ pub db_path: PathBuf,
pub db_log_dir: Option,
pub db_parallelism: u8,
pub daemon_auth: SensitiveAuth,
pub daemon_rpc_addr: SocketAddr,
+ pub daemon_p2p_addr: SocketAddr,
pub electrum_rpc_addr: SocketAddr,
pub monitoring_addr: SocketAddr,
pub wait_duration: Duration,
pub jsonrpc_timeout: Duration,
+ pub index_batch_size: usize,
pub index_lookup_limit: Option,
pub reindex_last_blocks: usize,
pub auto_reindex: bool,
@@ -143,6 +145,7 @@ pub struct Config {
pub skip_block_download_wait: bool,
pub disable_electrum_rpc: bool,
pub server_banner: String,
+ pub magic: Magic,
}
pub struct SensitiveAuth(pub Auth);
@@ -195,6 +198,16 @@ impl Config {
internal::prelude::Config::including_optional_config_files(default_config_files())
.unwrap_or_exit();
+ let db_subdir = match config.network {
+ Network::Bitcoin => "bitcoin",
+ Network::Testnet => "testnet",
+ Network::Testnet4 => "testnet4",
+ Network::Regtest => "regtest",
+ Network::Signet => "signet",
+ };
+
+ config.db_dir.push(db_subdir);
+
let default_daemon_rpc_port = match config.network {
Network::Bitcoin => 8332,
Network::Testnet => 18332,
@@ -202,6 +215,13 @@ impl Config {
Network::Regtest => 18443,
Network::Signet => 38332,
};
+ let default_daemon_p2p_port = match config.network {
+ Network::Bitcoin => 8333,
+ Network::Testnet => 18333,
+ Network::Testnet4 => 48333,
+ Network::Regtest => 18444,
+ Network::Signet => 38333,
+ };
let default_electrum_port = match config.network {
Network::Bitcoin => 50001,
Network::Testnet => 60001,
@@ -217,10 +237,25 @@ impl Config {
Network::Signet => 34224,
};
+ let magic = match config.magic {
+ Some(magic_hex) => magic_hex.parse().unwrap_or_else(|error| {
+ eprintln!(
+ "Error: magic '{}' is not a valid hex string: {}",
+ magic_hex, error
+ );
+ std::process::exit(1);
+ }),
+ None => config.network.magic(),
+ };
+
let daemon_rpc_addr: SocketAddr = config.daemon_rpc_addr.map_or(
(DEFAULT_SERVER_ADDRESS, default_daemon_rpc_port).into(),
ResolvAddr::resolve_or_exit,
);
+ let daemon_p2p_addr: SocketAddr = config.daemon_p2p_addr.map_or(
+ (DEFAULT_SERVER_ADDRESS, default_daemon_p2p_port).into(),
+ ResolvAddr::resolve_or_exit,
+ );
let electrum_rpc_addr: SocketAddr = config.electrum_rpc_addr.map_or(
(DEFAULT_SERVER_ADDRESS, default_electrum_port).into(),
ResolvAddr::resolve_or_exit,
@@ -304,15 +339,17 @@ impl Config {
let config = Config {
network: config.network,
- db_dir: config.db_dir,
+ db_path: config.db_dir,
db_log_dir: config.db_log_dir,
db_parallelism: config.db_parallelism,
daemon_auth,
daemon_rpc_addr,
+ daemon_p2p_addr,
electrum_rpc_addr,
monitoring_addr,
wait_duration: Duration::from_secs(config.wait_duration_secs),
jsonrpc_timeout: Duration::from_secs(config.jsonrpc_timeout_secs),
+ index_batch_size: config.index_batch_size,
index_lookup_limit,
reindex_last_blocks: config.reindex_last_blocks,
auto_reindex: config.auto_reindex,
@@ -321,6 +358,7 @@ impl Config {
skip_block_download_wait: config.skip_block_download_wait,
disable_electrum_rpc: config.disable_electrum_rpc,
server_banner: config.server_banner,
+ magic,
};
eprintln!(
"Starting electrs {} on {} {} with {:?}",
@@ -344,6 +382,9 @@ mod tests {
#[test]
fn test_auth_debug() {
+ let auth = Auth::None;
+ assert_eq!(format!("{:?}", SensitiveAuth(auth)), "None");
+
let auth = Auth::CookieFile(Path::new("/foo/bar/.cookie").to_path_buf());
assert_eq!(
format!("{:?}", SensitiveAuth(auth)),
diff --git a/src/daemon.rs b/src/daemon.rs
index f31337e..40df169 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -1,10 +1,11 @@
use anyhow::{Context, Result};
-use crate::bitcoin::{
- self, consensus::deserialize, consensus::encode::serialize_hex, hashes::hex::FromHex, Amount,
- BlockHash, Transaction, Txid,
-};
-use crate::types::Auth;
+use bitcoin::consensus::encode::serialize_hex;
+use bitcoin::{consensus::deserialize, hashes::hex::FromHex};
+use bitcoin::{Amount, BlockHash, Transaction, Txid};
+use bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi};
+use crossbeam_channel::Receiver;
+use parking_lot::Mutex;
use serde::Serialize;
use serde_json::{json, value::RawValue, Value};
@@ -12,62 +13,52 @@ use std::fs::File;
use std::io::Read;
use std::path::Path;
-use crate::{config::Config, signals::ExitFlag};
+use crate::{
+ chain::{Chain, NewHeader},
+ config::Config,
+ metrics::Metrics,
+ p2p::Connection,
+ signals::ExitFlag,
+ types::SerBlock,
+};
enum PollResult {
Done(Result<()>),
Retry,
}
-#[derive(Deserialize)]
-pub struct GetMempoolEntryResultFees {
- /// Transaction fee in BTC
- #[serde(with = "bitcoin::amount::serde::as_btc")]
- pub base: Amount,
-}
-
-#[derive(Deserialize)]
-pub struct GetMempoolEntryResult {
- /// Virtual transaction size as defined in BIP 141. This is different from actual serialized
- /// size for witness transactions as witness data is discounted.
- #[serde(alias = "size")]
- pub vsize: u64,
- /// Fee information
- pub fees: GetMempoolEntryResultFees,
- /// Unconfirmed transactions used as inputs for this transaction
- pub depends: Vec,
-}
-
-#[derive(Deserialize)]
-pub struct GetBlockchainInfoResult {
- pub blocks: u64,
- pub headers: u64,
- pub initialblockdownload: bool,
- pub pruned: bool,
-}
-
-#[derive(Deserialize)]
-pub struct EstimateSmartFeeResult {
- /// Estimate fee rate in BTC/kB.
- #[serde(
- default,
- rename = "feerate",
- skip_serializing_if = "Option::is_none",
- with = "bitcoin::amount::serde::as_btc::opt"
- )]
- pub fee_rate: Option,
-}
-
-#[derive(Deserialize)]
-pub struct GetNetworkInfoResult {
- #[serde(rename = "relayfee", with = "bitcoin::amount::serde::as_btc")]
- pub relay_fee: Amount,
-}
-
-#[derive(Deserialize)]
-pub struct GetMempoolInfoResult {
- /// True if the mempool is fully loaded
- pub loaded: Option,
+fn rpc_poll(client: &mut Client, skip_block_download_wait: bool) -> PollResult {
+ match client.get_blockchain_info() {
+ Ok(info) => {
+ if skip_block_download_wait {
+ // bitcoind RPC is available, don't wait for block download to finish
+ return PollResult::Done(Ok(()));
+ }
+ let left_blocks = info.headers - info.blocks;
+ if info.initial_block_download || left_blocks > 0 {
+ info!(
+ "waiting for {} blocks to download{}",
+ left_blocks,
+ if info.initial_block_download {
+ " (IBD)"
+ } else {
+ ""
+ }
+ );
+ return PollResult::Retry;
+ }
+ PollResult::Done(Ok(()))
+ }
+ Err(err) => {
+ if let Some(e) = extract_bitcoind_error(&err) {
+ if e.code == -28 {
+ debug!("waiting for RPC warmup: {}", e.message);
+ return PollResult::Retry;
+ }
+ }
+ PollResult::Done(Err(err).context("daemon not available"))
+ }
+ }
}
fn read_cookie(path: &Path) -> Result<(String, String)> {
@@ -88,7 +79,7 @@ fn read_cookie(path: &Path) -> Result<(String, String)> {
Ok((parts[0].to_owned(), parts[1].to_owned()))
}
-fn jsonrpc_client(config: &Config) -> Result {
+fn rpc_connect(config: &Config) -> Result {
let rpc_url = format!("http://{}", config.daemon_rpc_addr);
// Allow RPC calls to take longer before timing out.
// See https://github.com/romanz/electrs/issues/495 for more details.
@@ -96,30 +87,36 @@ fn jsonrpc_client(config: &Config) -> Result {
.url(&rpc_url)?
.timeout(config.jsonrpc_timeout);
let builder = match config.daemon_auth.get_auth() {
+ Auth::None => builder,
Auth::UserPass(user, pass) => builder.auth(user, Some(pass)),
Auth::CookieFile(path) => {
let (user, pass) = read_cookie(&path)?;
builder.auth(user, Some(pass))
}
};
- Ok(jsonrpc::Client::with_transport(builder.build()))
+ Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport(
+ builder.build(),
+ )))
}
pub struct Daemon {
- client: jsonrpc::Client,
+ p2p: Mutex,
+ rpc: Client,
}
impl Daemon {
- pub(crate) fn connect(config: &Config, exit_flag: &ExitFlag) -> Result {
- let daemon = Daemon {
- client: jsonrpc_client(config)?,
- };
+ pub(crate) fn connect(
+ config: &Config,
+ exit_flag: &ExitFlag,
+ metrics: &Metrics,
+ ) -> Result {
+ let mut rpc = rpc_connect(config)?;
loop {
exit_flag
.poll()
.context("bitcoin RPC polling interrupted")?;
- match daemon.rpc_poll(config.skip_block_download_wait) {
+ match rpc_poll(&mut rpc, config.skip_block_download_wait) {
PollResult::Done(result) => {
result.context("bitcoind RPC polling failed")?;
break; // on success, finish polling
@@ -130,85 +127,33 @@ impl Daemon {
}
}
- Ok(daemon)
- }
-
- fn call serde::de::Deserialize<'a>>(
- &self,
- cmd: &str,
- args: &[serde_json::Value],
- ) -> Result {
- let raw = serde_json::value::to_raw_value(args)?;
- let req = self.client.build_request(cmd, Some(&*raw));
- let resp = self.client.send_request(req)?;
- resp.result()
- }
-
- fn batch_request(&self, name: &str, items: &[T]) -> Result>>
- where
- T: Serialize,
- {
- debug!("calling {} on {} items", name, items.len());
- let args: Vec> = items
- .iter()
- .map(|item| jsonrpc::try_arg([item]).context("failed to serialize into JSON"))
- .collect::>>()?;
- let reqs: Vec = args
- .iter()
- .map(|arg| self.client.build_request(name, Some(arg)))
- .collect();
- match self.client.send_batch(&reqs) {
- Ok(values) => {
- assert_eq!(items.len(), values.len());
- Ok(values)
- }
- Err(err) => bail!("batch {} request failed: {}", name, err),
+ let network_info = rpc.get_network_info()?;
+ if network_info.version < 21_00_00 {
+ bail!("electrs requires bitcoind 0.21+");
}
- }
-
- fn rpc_poll(&self, skip_block_download_wait: bool) -> PollResult {
- match self.call::("getblockchaininfo", &[]) {
- Ok(info) => {
- if info.pruned {
- return PollResult::Done(Err(anyhow!(
- "electrs requires non-pruned bitcoind node"
- )));
- }
- if skip_block_download_wait {
- // bitcoind RPC is available, don't wait for block download to finish
- return PollResult::Done(Ok(()));
- }
- let left_blocks = info.headers - info.blocks;
- if info.initialblockdownload || left_blocks > 0 {
- info!(
- "waiting for {} blocks to download{}",
- left_blocks,
- if info.initialblockdownload {
- " (IBD)"
- } else {
- ""
- }
- );
- return PollResult::Retry;
- }
- PollResult::Done(Ok(()))
- }
- Err(err) => {
- if let Some(e) = extract_bitcoind_error(&err) {
- if e.code == -28 {
- debug!("waiting for RPC warmup: {}", e.message);
- return PollResult::Retry;
- }
- }
- PollResult::Done(Err(err).context("daemon not available"))
- }
+ if !network_info.network_active {
+ bail!("electrs requires active bitcoind p2p network");
}
+ let info = rpc.get_blockchain_info()?;
+ if info.pruned {
+ bail!("electrs requires non-pruned bitcoind node");
+ }
+
+ let p2p = Mutex::new(Connection::connect(
+ config.daemon_p2p_addr,
+ metrics,
+ config.magic,
+ )?);
+ Ok(Self { p2p, rpc })
}
pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result