Waterfalls is a Rust project providing blockchain data to Liquid and Bitcoin light-client wallets.
Use Nix (defined in flake.nix): nix develop or direnv allow.
Provides: Rust toolchain (via rust-overlay), RocksDB, OpenSSL, bitcoind, elementsd, libclang, rust-analyzer.
When the nix env is not already active (e.g. sandbox), prefer direnv exec . <command>
(e.g. direnv exec . cargo check) over nix develop --command <command> — it uses the
cached nix-direnv environment and avoids flake re-evaluation overhead.
cargo build --quiet # Debug build with suppressed outputs, reduce useless tokens
cargo build --release # Release build
cargo check --quiet # Fast type-check
cargo check --no-default-features # Check without default features
cargo check --no-default-features --features test_env
cargo check --benches
cargo check --testsThere are unit tests and integration tests. The former are launched with --lib the latter takes longer and cannot be launched in the sandbox (they must spawn nodes bindings on port which is not permitted in the sandbox), thus they require escalation. Run integration tests only if you are requested or really need to prove a change.
cargo test --lib --quiet # Run all tests (uses default features: test_env, db)
cargo test test_name # Run a single test by name
cargo test -- --nocapture # Show stdout/stderr
cargo test -- --ignored # Run ignored tests (require internet)
cargo test --test integration # Run only integration tests
cargo bench --features bench_test # Run benchmarks (criterion)test_env(default) — enablesbitcoinddep for tests requiring local nodesdb(default) — enables RocksDB storage backendsynced_node— tests requiring a locally running synced nodebench_test— long-running benchmark testsexamine_logs— log inspection tests
export BITCOIND_EXEC=/path/to/bitcoind # Provided by nix develop
export ELEMENTSD_EXEC=/path/to/elementsd # Provided by nix develop
export RUST_LOG=debug # Optional: enable debug loggingcargo fmt # Format code (default rustfmt settings)
cargo clippy # Lint
cargo clippy -- -D warnings # Lint, fail on warnings (CI enforced)No rustfmt.toml or clippy.toml — default settings are used.
Grouped in this order (separated by blank lines when practical):
std::— standard library- External crates —
anyhow,elements,hyper,serde,tokio, etc. crate::/super::— internal modules
Use absolute paths: use waterfalls::be::Address (in tests/benches), use crate::be::Address (within the crate).
- Application-level:
anyhow::Resultfor fallible operations in fetch, threads, startup. - Server routes: custom
Errorenum insrc/server/mod.rsmapped to HTTP status codes.CannotDecrypt→ 422,BodyTooLarge→ 413,BodyReadTimeout→ 408, input errors → 400, others → 500.
- Fetch layer: custom
Errorenum insrc/fetch.rs(TxNotFound,BlockNotFound, etc.). - Unrecoverable:
error_panic!macro (defined insrc/lib.rs) — logs vialog::error!then panics. - Log errors with
log::error!before returning them.
- Use the
logcrate:log::info!,log::warn!,log::error!,log::debug! - Initialized via
env_loggerinmain.rs(default filter:info) - For systemd integration: set
RUST_LOG_STYLE=SYSTEMD
- Runtime:
tokiowithrt-multi-thread - Use
tokio::select!for concurrent operations (e.g., signal handling) - Use
#[tokio::test]for async tests
- JSON:
serde/serde_jsonwithSerialize/Deserializederives - CBOR:
minicborwithEncode/Decodederives and customwithhelpers insrc/cbor.rs - Field annotations:
#[cbor(n(X))]for CBOR field indices,#[serde(skip_serializing_if = ...)]
#[tokio::test]for async tests#[cfg(feature = "test_env")]gates tests needing bitcoind/elementsd#[cfg(all(feature = "test_env", feature = "db"))]for DB-backed integration tests#[ignore = "requires internet"]for tests hitting remote endpointsenv_logger::try_init()at test start (ignore the error if already initialized)- Test infrastructure in
src/test_env.rs:TestEnv,WaterfallClient,launch(),launch_with_node() - Integration tests in
tests/integration.rsuselaunch_memory()/test_env::launch()to spin up node + server - Test name should avoid common prefix in the name, so that specifiying the full name of a test, only one test run
src/
├── lib.rs # Library root: types, error_panic! macro, prometheus metrics
├── main.rs # Binary entry: clap parsing, logging, signal handling
├── fetch.rs # Blockchain data fetching (esplora / local node REST)
├── cbor.rs # CBOR encoding helpers for block hashes
├── test_env.rs # Test utilities (TestEnv, WaterfallClient)
├── be/ # Backend types (Address, Block, BlockHeader, Descriptor, Tx, Txid)
├── server/ # HTTP server: Arguments (clap), Network, Error enum, routing,
│ # state, mempool, signing, encryption, derivation_cache, preload
├── store/ # Store trait + AnyStore, memory.rs, db.rs (RocksDB, behind `db` feature)
└── threads/ # Background tasks: block indexing, mempool sync
build.rs # Injects GIT_COMMIT_HASH at build time
tests/integration.rs # Integration tests
benches/benches.rs # Criterion benchmarks
Runs on push/PR to master:
- tests: downloads bitcoind 28.0 & elementsd 23.2.4, runs
cargo testandcargo test -- --ignored - checks:
cargo checkwith various feature combinations - nix:
nix build .with cachix
Runs on push to master, tag creation, and manual dispatch:
- Push to
masterpublishesblockstream/waterfalls:latest - Tag push publishes
blockstream/waterfalls:<git-tag>
The workflow builds native images on:
ubuntu-latestforlinux/amd64ubuntu-24.04-armforlinux/arm64
Then it creates a multi-arch manifest tag from per-arch tags:
blockstream/waterfalls:<final-tag>-amd64blockstream/waterfalls:<final-tag>-arm64blockstream/waterfalls:<final-tag>(manifest list with both architectures)
Required GitHub repository secret:
DOCKERHUB_TOKEN
From .cursor/rules/my-custom-rule.mdc (always applied):
- The developer uses a Nix environment from
flake.nix— use it when proposing commands - Add new structs at the end of files, or just before
#[cfg(test)] mod testsif present - Never add new dependencies unless explicitly asked
cargo run -- --network liquid --use-esplora # Run server against esplora
cargo test --features "test_env db" # Run tests with DB backend
nix build .#dockerImage && docker load < result # Build Docker image
cargo bench --features bench_test # Run benchmarks