Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# CLAUDE.md

## Project Overview

Electrs (Esplora variant) is a blockchain index engine and HTTP API for Bitcoin (and Liquid/Litecoin/Dogecoin) written in Rust. It is the backend for the [Esplora block explorer](https://github.com/Blockstream/esplora) powering blockstream.info. Forked from [romanz/electrs](https://github.com/romanz/electrs), it adds an HTTP REST API, extended indexes, and Elements/Liquid support, as well as Litecoin and Dogecoin support.

## Build & Run

```bash
# Build (requires clang, cmake, and Rust 1.75.0 via rust-toolchain.toml)
cargo build --release

# Run (requires a running bitcoind, no txindex needed)
cargo run --release --bin electrs -- -vvvv --daemon-dir ~/.bitcoin

# Build with Liquid support
cargo build --features liquid --release

# Build with Litecoin support
cargo build --features litecoin --release

# Run with Litecoin (requires a running litecoind)
cargo run --features litecoin --release --bin electrs -- -vvvv --daemon-dir ~/.litecoin

# Build with Dogecoin support
cargo build --features dogecoin --release

# Run with Dogecoin (requires a running dogecoind)
cargo run --features dogecoin --release --bin electrs -- -vvvv --daemon-dir ~/.dogecoin

# Build with OpenTelemetry tracing
cargo build --features otlp-tracing --release
```

## Testing

```bash
# Run all tests (requires bitcoind binary available in PATH or via BITCOIND_EXE)
RUST_LOG=debug cargo test

# Run Liquid tests
RUST_LOG=debug cargo test --features liquid

# Run tests with Bitcoin Core 28+ compatibility
RUST_LOG=debug cargo test --features bitcoind_28_0

# Run specific ignored test
cargo test -- --include-ignored test_electrum_raw

# Run benchmarks (requires bench feature)
cargo bench --features bench
```

Integration tests in `tests/` spin up a real bitcoind (or elementsd) instance, create a temporary RocksDB, index blocks, and run queries against the REST and Electrum APIs.

## Formatting & Linting

```bash
# Format check (used by pre-commit hook)
cargo fmt --all -- --check

# Format
cargo fmt --all

# Clippy
cargo clippy --all-targets
```

The pre-commit hook (`.hooks/pre-commit`) runs `cargo +stable fmt --all -- --check`.

## Architecture

### Key Modules (`src/`)

- **`bin/electrs.rs`** — Main entry point. Starts the daemon connection, indexer, mempool tracker, REST server, and Electrum RPC server.
- **`new_index/`** — Core indexing engine:
- `schema.rs` — Index schema definitions: `Store`, `Indexer`, `ChainQuery`. Three RocksDB databases: `txstore`, `history`, `cache`.
- `db.rs` — RocksDB wrapper and low-level operations.
- `mempool.rs` — Mempool tracking and indexing.
- `query.rs` — High-level query interface combining chain and mempool data.
- `fetch.rs` — Block fetching from blk*.dat files or via JSON-RPC.
- `zmq.rs` — ZMQ notifications for new blocks.
- **`rest.rs`** — HTTP REST API server (tiny_http-based).
- **`electrum/`** — Electrum JSON-RPC protocol server.
- `server.rs` — TCP server and connection handling.
- `client.rs` — Per-client state and request processing.
- **`daemon.rs`** — Bitcoin Core JSON-RPC client.
- **`config.rs`** — CLI argument parsing and configuration.
- **`chain.rs`** — Blockchain type aliases and network definitions.
- **`elements/`** — Liquid/Elements-specific code (behind `liquid` feature flag).
- **`util/`** — Helpers for transactions, scripts, fees, merkle proofs, and block parsing.
- `litecoin_addr.rs` — Litecoin address encoding/decoding (bech32 `ltc1`/`tltc1`/`rltc1` and base58check with Litecoin version bytes). Behind `litecoin` feature flag.
- `dogecoin.rs` — Dogecoin address encoding/decoding (base58check with Dogecoin version bytes, no SegWit) and AuxPoW block deserialization. Behind `dogecoin` feature flag.
- **`electrs_macros/`** — Proc-macro crate for optional OTLP tracing instrumentation.

### Data Flow

1. **Indexing**: Blocks are fetched (from blk*.dat files for initial sync, or JSON-RPC for incremental updates) and indexed into three RocksDB databases (`txstore` → `history` → `cache`).
2. **Serving**: The REST API and Electrum RPC server query the indexed data via `ChainQuery` (on-chain) and `Mempool` (unconfirmed), unified through `Query`.
3. **Mempool**: Synced from bitcoind and re-synced on each main loop iteration (every 5 seconds or on ZMQ notification).

### Feature Flags

- `liquid` — Liquid/Elements network support (mutually exclusive with `litecoin`)
- `litecoin` — Litecoin network support (mutually exclusive with `liquid` and `dogecoin`). Networks: litecoin, litecointestnet, litecoinregtest. Note: MWEB (MimbleWimble Extension Blocks) is not yet supported.
- `dogecoin` — Dogecoin network support (mutually exclusive with `liquid` and `litecoin`). Networks: dogecoin, dogecointestnet, dogecoinregtest. Handles AuxPoW (Auxiliary Proof of Work) merged-mining block format.
- `electrum-discovery` — Electrum server peer discovery
- `otlp-tracing` — OpenTelemetry tracing export
- `bitcoind_28_0` — Bitcoin Core 28.0+ compatibility
- `bench` — Enable benchmarks

### Database Schema

See `doc/schema.md` for the full RocksDB key/value schema. Key prefixes: `B` (block headers), `T` (raw txs), `O` (outpoints), `H` (history), `C` (confirmations), `S` (spending), `X` (block txids), `M` (block metadata).

## Configuration

Configuration is done via CLI args. Key options:
- `--daemon-dir` — bitcoind data directory
- `--db-dir` — electrs database directory
- `--http-addr` — REST API listen address (default: 127.0.0.1:3000)
- `--electrum-rpc-addr` — Electrum RPC listen address (default: 127.0.0.1:50001)
- `--lightmode` — reduce disk usage by querying bitcoind for raw txs on demand
- `--network` — bitcoin network (mainnet/testnet/regtest/signet/liquid/litecoin/litecointestnet/litecoinregtest/dogecoin/dogecointestnet/dogecoinregtest)
- `--cors` — CORS origins for HTTP API

## Workspace

This is a Cargo workspace with two members:
- `electrs` (root) — main crate
- `electrs_macros` — proc-macro crate for OTLP tracing
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ default-run = "electrs"

[features]
liquid = ["elements"]
litecoin = []
dogecoin = []
electrum-discovery = ["electrum-client"]
bench = []
otlp-tracing = [
Expand Down
153 changes: 153 additions & 0 deletions plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Plan: Add Litecoin Support to Electrs

## Overview

Add Litecoin (mainnet, testnet, regtest) support to electrs as a compile-time feature flag `litecoin`, following the same pattern used by the `liquid` feature. Litecoin Core's RPC API is compatible with Bitcoin Core's, and Litecoin uses the same transaction/block serialization format, so the core indexing engine works without changes. The main work is in network constants, address handling, and configuration.

## Key Litecoin Differences from Bitcoin
- Different genesis block hash
- Different network magic bytes (`0xdbb6c0fb` mainnet, `0xfcc1b7dc` testnet)
- Different default RPC port (9332 mainnet, 19332 testnet)
- Different address version bytes (P2PKH `0x30`/`L`, P2SH `0x32`/`M` or `0x05`/`3`)
- Different default data directory (`~/.litecoin/`)
- 2.5 minute block time (does not affect indexing logic)
- Scrypt PoW (does not affect indexing — only miners care)
- MWEB (MimbleWimble Extension Blocks) — **out of scope for initial implementation**; MWEB transactions are extension blocks that standard RPC returns as normal, but full MWEB peg-in/peg-out tracking would be a follow-up

## Implementation Steps

### Step 1: Add `litecoin` feature flag to `Cargo.toml`

**File: `Cargo.toml`**

- Add `litecoin = []` to `[features]`
- No new dependencies needed — Litecoin uses the same serialization as Bitcoin, and `rust-bitcoin` types work for Litecoin block/tx data since the formats are identical

### Step 2: Extend the `Network` enum in `chain.rs`

**File: `src/chain.rs`**

- Add three new variants gated by `#[cfg(feature = "litecoin")]`:
- `Litecoin` (mainnet)
- `LitecoinTestnet`
- `LitecoinRegtest`
- Implement `magic()` for Litecoin variants:
- Litecoin mainnet: `0xdbb6c0fb`
- Litecoin testnet: `0xfcc1b7dc`
- Litecoin regtest: `0xdab5bffa` (same as Bitcoin regtest)
- Implement `genesis_hash()` for Litecoin:
- Mainnet: `12a765e31ffd4059bada1e25190f6e98c0fe1666a68542019d52529ccc51ee1d`
- Testnet4: `a0293e4eeb3da6e6f56f81ed595f57880d1a21569e13eefdd951284b5a626649`
- Regtest: use the Bitcoin regtest genesis hash (same genesis block structure)
- Implement `is_regtest()` for `LitecoinRegtest`
- Add to `Network::names()`: `"litecoin"`, `"litecointestnet"`, `"litecoinregtest"`
- Add `From<&str>` mappings for the new network names
- For the `From<Network> for BNetwork` and `From<BNetwork> for Network` conversions: Litecoin variants should NOT convert to/from `BNetwork` since they aren't Bitcoin networks. These trait impls are gated behind `#[cfg(not(feature = "liquid"))]` — they need to also be gated behind `#[cfg(not(feature = "litecoin"))]`, OR the Litecoin feature should be mutually exclusive with default Bitcoin (like `liquid` is). **Decision: Make `litecoin` mutually exclusive with default Bitcoin mode, following the same `cfg` pattern as `liquid`.**

### Step 3: Update configuration defaults in `config.rs`

**File: `src/config.rs`**

- Add Litecoin variants to all `match network_type` blocks:
- **Daemon RPC port**: 9332 (mainnet), 19332 (testnet), 19443 (regtest)
- **Electrum RPC port**: 50001 (mainnet), 60001 (testnet), 60401 (regtest)
- **HTTP port**: 3000 (mainnet), 3001 (testnet), 3002 (regtest)
- **Monitoring port**: 4224 (mainnet), 14224 (testnet), 24224 (regtest)
- Update `get_network_subdir()`:
- `Litecoin` → `None` (top-level `~/.litecoin/`)
- `LitecoinTestnet` → `Some("testnet4")`
- `LitecoinRegtest` → `Some("regtest")`
- Update default `daemon_dir` to `~/.litecoin` when litecoin feature is enabled
- Update help text references from "Bitcoind" / "bitcoind" to be more generic or add litecoin-specific text

### Step 4: Handle address encoding/validation

**File: `src/util/script.rs`**

- The `ScriptToAddr` implementation for Bitcoin uses `bitcoin::Address::from_script(self, bitcoin::Network::from(network))`. Since Litecoin addresses use different version bytes than Bitcoin, we cannot use `bitcoin::Network` directly.
- **Approach**: For Litecoin, implement custom address-to-string conversion that handles Litecoin's address prefixes:
- Litecoin P2PKH: version byte `0x30` (addresses start with `L`)
- Litecoin P2SH: version byte `0x32` (addresses start with `M`) and legacy `0x05` (start with `3`)
- Litecoin Bech32: `ltc1` prefix (P2WPKH and P2WSH)
- Testnet uses `tltc1` for bech32, `0x6f` for P2PKH, `0xc4` for P2SH

**File: `src/rest.rs`**

- Update `address_to_scripthash()` to handle Litecoin address parsing:
- Parse Litecoin bech32 addresses (`ltc1...` / `tltc1...`)
- Parse base58 addresses with Litecoin version bytes
- Validate that the address matches the configured network
- This likely requires a small helper function or use of a litecoin address parsing crate

### Step 5: Update daemon.rs for Litecoin compatibility

**File: `src/daemon.rs`**

- The version check `network_info.version < 16_00_00` may need adjustment for Litecoin Core version numbers (Litecoin Core uses its own versioning scheme, e.g., 21.x maps differently)
- Update log messages: change "bitcoind" references to be network-aware or generic ("daemon")
- The RPC API is otherwise identical — `getblockchaininfo`, `getblock`, `getrawtransaction`, etc. all work the same

### Step 6: Update Electrum discovery default servers

**File: `src/electrum/discovery/default_servers.rs`**

- Add `#[cfg(feature = "litecoin")]` blocks with Litecoin Electrum server entries for `Network::Litecoin` and `Network::LitecoinTestnet`
- Known Litecoin Electrum servers can be sourced from Electrum-LTC server lists

### Step 7: Ensure feature flag mutual exclusivity

**File: `Cargo.toml`** and across `src/`

- The `litecoin` feature must be mutually exclusive with `liquid` (and ideally with default Bitcoin mode)
- Follow the same `#[cfg(feature = "litecoin")]` / `#[cfg(not(feature = "litecoin"))]` pattern used by `liquid`
- Update `#[cfg(not(feature = "liquid"))]` guards to also exclude litecoin: `#[cfg(all(not(feature = "liquid"), not(feature = "litecoin")))]` for Bitcoin-only code paths
- Add compile-time check (or document) that `litecoin` and `liquid` cannot be combined

### Step 8: Update tests

**Files: `tests/common.rs`, `tests/rest.rs`, `tests/electrum.rs`**

- Integration tests use `bitcoind` crate to spawn a Bitcoin Core node. For Litecoin, we'd need a `litecoind` test fixture. This is complex and can be deferred.
- Add unit tests for:
- Litecoin genesis hash correctness
- Litecoin magic byte correctness
- Litecoin address parsing and validation (bech32 `ltc1` and base58)
- Network name parsing round-trip

### Step 9: Update documentation

**Files: `README.md`, `CLAUDE.md`**

- Document the `litecoin` feature flag
- Add build/run instructions for Litecoin mode:
```bash
cargo run --features litecoin --release --bin electrs -- -vvvv --daemon-dir ~/.litecoin
```
- Document MWEB limitation (not yet supported)

## Files Changed (Summary)

| File | Changes |
|------|---------|
| `Cargo.toml` | Add `litecoin` feature flag |
| `src/chain.rs` | Network enum variants, magic bytes, genesis hashes, conversions |
| `src/config.rs` | Default ports, data dirs, network subdirs, help text |
| `src/util/script.rs` | Litecoin address encoding (bech32 `ltc1`, base58 version bytes) |
| `src/rest.rs` | Litecoin address parsing in `address_to_scripthash()` |
| `src/daemon.rs` | Version check adjustment, generic log messages |
| `src/electrum/discovery/default_servers.rs` | Litecoin Electrum server list |
| `tests/` | Unit tests for Litecoin constants and address handling |
| `README.md` | Documentation for Litecoin feature |
| `CLAUDE.md` | Update feature flags section |

## Out of Scope (Future Work)

- **MWEB support**: MimbleWimble Extension Blocks require tracking peg-in/peg-out transactions between the main chain and the extension block. This is analogous to Liquid's peg handling and would be a significant follow-up feature.
- **Litecoin integration tests**: Requires a `litecoind` test harness crate similar to the `bitcoind` crate used for Bitcoin tests.
- **Scrypt PoW verification**: Not needed — electrs trusts the daemon's chain validation and doesn't verify PoW.

## Risk Assessment

- **Low risk**: Network constants, ports, directory paths — straightforward additions following existing patterns.
- **Medium risk**: Address handling — Litecoin uses different bech32 HRP (`ltc1` vs `bc1`) and different base58 version bytes. The `rust-bitcoin` crate's `Address` type is Bitcoin-specific and won't natively handle Litecoin addresses. We may need to add a dependency like `litecoin-address` or implement custom parsing.
- **Low risk**: RPC compatibility — Litecoin Core's JSON-RPC API mirrors Bitcoin Core's closely. The same `getblockchaininfo`, `getblock`, `getrawmempool`, etc. calls work identically.
12 changes: 6 additions & 6 deletions src/bin/electrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ extern crate log;

extern crate electrs;

use crossbeam_channel::{self as channel};
use error_chain::ChainedError;
use std::{env, process, thread};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use bitcoin::hex::DisplayHex;
use rand::{rng, RngCore};
use crossbeam_channel::{self as channel};
use electrs::{
config::Config,
daemon::Daemon,
Expand All @@ -21,6 +16,11 @@ use electrs::{
rest,
signal::Waiter,
};
use error_chain::ChainedError;
use rand::{rng, RngCore};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use std::{env, process, thread};

#[cfg(feature = "otlp-tracing")]
use electrs::otlp_trace;
Expand Down
5 changes: 4 additions & 1 deletion src/bin/popular-scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ extern crate electrs;

use bitcoin::hex::DisplayHex;
use electrs::{
config::Config, metrics::Metrics, new_index::{Store, TxHistoryKey}, util::bincode
config::Config,
metrics::Metrics,
new_index::{Store, TxHistoryKey},
util::bincode,
};

fn main() {
Expand Down
Loading