diff --git a/Cargo.lock b/Cargo.lock
index 7a37539..70abbb5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -47,9 +47,9 @@ dependencies = [
[[package]]
name = "base64"
-version = "0.13.1"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bech32"
@@ -57,6 +57,23 @@ 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"
@@ -79,9 +96,9 @@ dependencies = [
[[package]]
name = "bitcoin"
-version = "0.32.8"
+version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66"
+checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3"
dependencies = [
"base58ck",
"bech32",
@@ -139,37 +156,12 @@ dependencies = [
[[package]]
name = "bitcoin_slices"
-version = "0.10.0"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943d4257fdfc85afe2a13d2b539a5213e97bb978329dd79b624acbce73042fb"
+checksum = "82b80fcc031ed36f91c31639a4d97acb1487985fdee34565651b2f52f4346859"
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]]
@@ -178,15 +170,6 @@ 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"
@@ -196,6 +179,12 @@ 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"
@@ -291,15 +280,6 @@ 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"
@@ -334,37 +314,17 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
[[package]]
name = "ctrlc"
-version = "3.5.1"
+version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790"
+checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
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"
@@ -409,10 +369,8 @@ name = "electrs"
version = "0.11.1"
dependencies = [
"anyhow",
- "bitcoin",
+ "bindex",
"bitcoin-test-data",
- "bitcoin_slices",
- "bitcoincore-rpc",
"configure_me",
"configure_me_codegen",
"crossbeam-channel",
@@ -420,11 +378,10 @@ dependencies = [
"dirs-next",
"env_logger",
"hex_lit",
+ "jsonrpc",
"log",
- "parking_lot",
"prometheus",
"rayon",
- "rust-rocksdb",
"serde",
"serde_derive",
"serde_json",
@@ -474,16 +431,6 @@ 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"
@@ -540,6 +487,22 @@ 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"
@@ -590,12 +553,11 @@ dependencies = [
[[package]]
name = "jsonrpc"
-version = "0.18.0"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf"
+checksum = "f106cca655869522988b976127096f0aa36e0f1a8ff1f1f425a5c85b61e59391"
dependencies = [
"base64",
- "minreq",
"serde",
"serde_json",
]
@@ -614,9 +576,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.178"
+version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libredox"
@@ -663,9 +625,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
@@ -679,22 +641,11 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "minreq"
-version = "2.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0d2aaba477837b46ec1289588180fabfccf0c3b1d1a0c6b1866240cd6cd5ce9"
-dependencies = [
- "log",
- "serde",
- "serde_json",
-]
-
[[package]]
name = "nix"
-version = "0.30.1"
+version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
dependencies = [
"bitflags",
"cfg-if",
@@ -771,21 +722,18 @@ 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"
@@ -869,41 +817,11 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-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"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
@@ -1038,7 +956,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes",
- "rand",
"secp256k1-sys",
"serde",
]
@@ -1084,9 +1001,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1095,17 +1012,6 @@ 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"
@@ -1114,9 +1020,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e"
+checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d"
dependencies = [
"libc",
"signal-hook-registry",
@@ -1150,9 +1056,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.24.0"
+version = "3.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
+checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
dependencies = [
"fastrand",
"getrandom 0.3.3",
@@ -1231,12 +1137,6 @@ 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"
@@ -1249,18 +1149,43 @@ 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"
@@ -1410,26 +1335,6 @@ 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 8af0480..e19ad2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,46 +23,30 @@ spec = "internal/config_specification.toml"
[dependencies]
anyhow = "1.0"
-bitcoin = { version = "0.32.8", features = ["serde", "rand-std"] }
-bitcoin_slices = { version = "0.10.0", features = ["bitcoin", "sha2"] }
-bitcoincore-rpc = { version = "0.19.0" }
+bindex = { version = "0.1.2", default-features = false, features = ["bitcoin_serde"] }
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.11"
+rayon = "1.12"
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.1"
+ctrlc = "=3.5.2"
[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.24"
-
-[profile.release]
-lto = true
-
-[lints.clippy]
-uninlined_format_args = "allow" # TODO: https://github.com/romanz/electrs/issues/1199
+tempfile = "3.25"
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 0231276..eadb696 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 wget
+RUN apt-get install -qqy librocksdb-dev libsqlite3-dev libevent-dev libboost-dev wget
### Electrum Rust Server ###
FROM base AS electrs-build
@@ -17,16 +17,15 @@ ENV ROCKSDB_INCLUDE_DIR=/usr/include
ENV ROCKSDB_LIB_DIR=/usr/lib
RUN cargo install --locked --path .
-### Bitcoin Core ###
+### Bitcoin Core (custom build for /blockpart/ REST endpoint) ###
FROM base AS bitcoin-build
# Download
WORKDIR /build/bitcoin
-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 .
+
+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 .
FROM base AS result
# Copy the binaries
diff --git a/doc/config.md b/doc/config.md
index 3784b20..b234e72 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -5,11 +5,12 @@ If you use automated systems, refer to their documentation first!
### Bitcoind configuration
-Pruning must be turned **off** for `electrs` to work.
+REST API must be **enabled** for block indexing and transaction lookup.
+
+Pruning must be **disabled** 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.
@@ -21,7 +22,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 -txindex=0 -prune=0
+$ bitcoind -server=1 -rest=1 -txindex=0 -prune=0
```
### Electrs configuration
@@ -139,7 +140,7 @@ HiddenServiceVersion 3
HiddenServicePort 50001 127.0.0.1:50001
```
-If you use [the *beta* Debian repository](binaries.md#cnative-os-packages),
+If you use [the *beta* Debian repository](binaries.md#native-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:
@@ -162,7 +163,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#cnative-os-packages), you should skip this section,
+If you use [the *beta* Debian repository](binaries.md#native-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 1272ddb..59cf429 100644
--- a/doc/config_example.toml
+++ b/doc/config_example.toml
@@ -14,9 +14,6 @@ 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 392cdf5..3ac8229 100644
--- a/doc/install.md
+++ b/doc/install.md
@@ -1,21 +1,14 @@
## 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 build --locked --release
-$ ./target/release/electrs --version # should print the latest version
+$ git clone https://github.com/romanz/electrs && cd electrs
+$ cargo run --locked --release -- --version # build and print the latest version
```
-
-
-[](https://asciinema.org/a/XKznxilP4O7lCZiVZ9vZNd5vx?speed=3)
-
## Manual installation from source
**See below for automated/binary installation options.**
@@ -26,7 +19,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/) (0.21+)
+[latest Bitcoin Core](https://bitcoincore.org/en/download/) (31.0+)
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 ab8af29..901b775 100644
--- a/doc/schema.md
+++ b/doc/schema.md
@@ -1,53 +1 @@
-# 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` |
-
+See https://github.com/romanz/bindex-rs for more details.
diff --git a/doc/upgrading.md b/doc/upgrading.md
index a52bffd..fd060da 100644
--- a/doc/upgrading.md
+++ b/doc/upgrading.md
@@ -1,3 +1,20 @@
+### 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 e6da8c7..8ec9ee9 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -1,77 +1,63 @@
## Quickstart
-
-Assuming Bitcoin Core 0.21+ is installed on the same machine (with the standard configuration at `~/.bitcoin/bitcoin.conf`):
+Assuming Bitcoin Core 31+ is installed on the same machine (with the standard configuration at `~/.bitcoin/bitcoin.conf`):
```bash
-$ bitcoind -server=1 -prune=0 &
+$ bitcoind -server=1 -rest=1 -prune=0 &
$ # ... wait until the chain is synced (e.g. using `bitcoin-cli getblockchaininfo`)
-$ electrs --log-filters=INFO --db-dir ./db --daemon-dir ~/.bitcoin --network bitcoin
+$ RUST_LOG=INFO electrs --network bitcoin --db-dir ./db --daemon-dir ~/.bitcoin
```
-
-
-[](https://asciinema.org/a/zRNZp5HsBDi5rAlGWU7470Pzl?speed=3)
-
## Usage
-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):
+First index sync should take ~2 hours for ~800GB `.bitcoin/blocks/` @ July 2026 (on a 6-core CPU, 32 GB RAM, 2TB NVMe):
```bash
-$ du -ch ~/.bitcoin/blocks/blk*.dat | tail -n1
-336G total
+$ du -ch ~/.bitcoin/blocks/*.dat | tail -n1
+802G total
-$ ./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
+$ 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
```
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 ~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)).
+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)).
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 db/
-42G db/mainnet/
+$ du -h db/bitcoin/
+56G db/bitcoin/
```
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#cnative-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#native-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.
@@ -98,7 +84,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.9.0","1.4"]}
+{"id":0,"jsonrpc":"2.0","result":["electrs 0.12.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
deleted file mode 100644
index 0b7fd58..0000000
--- a/examples/tx_collisions.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-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 f4ae65e..f445f41 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 = "::bitcoin::Network"
+convert_into = "crate::bitcoin::Network"
doc = "Select Bitcoin network type ('bitcoin', 'testnet', 'testnet4', 'regtest' or 'signet')"
default = "Default::default()"
@@ -76,10 +76,6 @@ 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"
@@ -98,12 +94,6 @@ 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."
@@ -146,8 +136,3 @@ 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 c66a4c9..7a7fe5f 100755
--- a/server.sh
+++ b/server.sh
@@ -9,7 +9,7 @@ NETWORK=$1
shift
DB=${DB-./db}
-export RUST_LOG=${RUST_LOG-electrs=INFO}
+export RUST_LOG=${RUST_LOG-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
deleted file mode 100644
index b09a916..0000000
--- a/src/cache.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-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
deleted file mode 100644
index a55e50d..0000000
--- a/src/chain.rs
+++ /dev/null
@@ -1,261 +0,0 @@
-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 b844dda..d147408 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,5 @@
-use bitcoin::p2p::Magic;
-use bitcoin::Network;
-use bitcoincore_rpc::Auth;
+use crate::bitcoin::Network;
+use crate::types::Auth;
use dirs_next::home_dir;
use std::ffi::{OsStr, OsString};
@@ -17,7 +16,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)]
+ #![allow(unused_attributes, unused_imports, clippy::enum_variant_names)]
include!(concat!(env!("OUT_DIR"), "/configure_me_config.rs"));
}
@@ -122,21 +121,20 @@ 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_path: PathBuf,
+ pub db_dir: 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,
@@ -145,7 +143,6 @@ 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);
@@ -198,16 +195,6 @@ 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,
@@ -215,13 +202,6 @@ 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,
@@ -237,25 +217,10 @@ 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,
@@ -339,17 +304,15 @@ impl Config {
let config = Config {
network: config.network,
- db_path: config.db_dir,
+ db_dir: 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,
@@ -358,7 +321,6 @@ 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 {:?}",
@@ -382,9 +344,6 @@ 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 40df169..f31337e 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -1,11 +1,10 @@
use anyhow::{Context, Result};
-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 crate::bitcoin::{
+ self, consensus::deserialize, consensus::encode::serialize_hex, hashes::hex::FromHex, Amount,
+ BlockHash, Transaction, Txid,
+};
+use crate::types::Auth;
use serde::Serialize;
use serde_json::{json, value::RawValue, Value};
@@ -13,52 +12,62 @@ use std::fs::File;
use std::io::Read;
use std::path::Path;
-use crate::{
- chain::{Chain, NewHeader},
- config::Config,
- metrics::Metrics,
- p2p::Connection,
- signals::ExitFlag,
- types::SerBlock,
-};
+use crate::{config::Config, signals::ExitFlag};
enum PollResult {
Done(Result<()>),
Retry,
}
-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"))
- }
- }
+#[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 read_cookie(path: &Path) -> Result<(String, String)> {
@@ -79,7 +88,7 @@ fn read_cookie(path: &Path) -> Result<(String, String)> {
Ok((parts[0].to_owned(), parts[1].to_owned()))
}
-fn rpc_connect(config: &Config) -> Result {
+fn jsonrpc_client(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.
@@ -87,36 +96,30 @@ fn rpc_connect(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(Client::from_jsonrpc(jsonrpc::Client::with_transport(
- builder.build(),
- )))
+ Ok(jsonrpc::Client::with_transport(builder.build()))
}
pub struct Daemon {
- p2p: Mutex,
- rpc: Client,
+ client: jsonrpc::Client,
}
impl Daemon {
- pub(crate) fn connect(
- config: &Config,
- exit_flag: &ExitFlag,
- metrics: &Metrics,
- ) -> Result {
- let mut rpc = rpc_connect(config)?;
+ pub(crate) fn connect(config: &Config, exit_flag: &ExitFlag) -> Result {
+ let daemon = Daemon {
+ client: jsonrpc_client(config)?,
+ };
loop {
exit_flag
.poll()
.context("bitcoin RPC polling interrupted")?;
- match rpc_poll(&mut rpc, config.skip_block_download_wait) {
+ match daemon.rpc_poll(config.skip_block_download_wait) {
PollResult::Done(result) => {
result.context("bitcoind RPC polling failed")?;
break; // on success, finish polling
@@ -127,33 +130,85 @@ impl Daemon {
}
}
- let network_info = rpc.get_network_info()?;
- if network_info.version < 21_00_00 {
- bail!("electrs requires bitcoind 0.21+");
- }
- 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");
- }
+ Ok(daemon)
+ }
- let p2p = Mutex::new(Connection::connect(
- config.daemon_p2p_addr,
- metrics,
- config.magic,
- )?);
- Ok(Self { p2p, rpc })
+ 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),
+ }
+ }
+
+ 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"))
+ }
+ }
}
pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result