Files
bal-server/docs/04_modules_detail.md
svatantrya 69d877a360 docs: add comprehensive knowledge base and security audit
- Add docs/INDEX.md with navigable index and quick reference guides
- Add 9 knowledge base files covering project overview, Bitcoin domain,
  architecture, modules, API reference, database schema, deployment/security
- Update AGENTS.md with knowledge base reference and update policy
- Add tests/sql_injection_tests.rs with regression tests for SQL injection
- Fix SQL injection vulnerabilities in bal-pusher.rs:
  * Replace string-formatted UPDATE IN with loop + parameterized queries
  * Replace string-formatted UPDATE push_err with parameterized query
  * Add chain name validation in calculate_stats to prevent env var tampering
- Update .gitignore to exclude bal-pusher.env and bal-pusher.sh
2026-07-16 14:11:18 -04:00

148 lines
8.1 KiB
Markdown

# Module Details
## Quick Reference
- **What this file contains:** detailed analysis of each Rust module and binary, including source code references.
- **See also:** [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md), [09_references_and_links.md](09_references_and_links.md)
---
## `lib.rs`
**Location:** `src/lib.rs`
This is the root of the library crate. It simply exports two public modules:
- `pub mod db;` — the database interface
- `pub mod xpub;` — the extended public key utilities
It contains no application logic.
---
## `db.rs` (Database Interface)
**Location:** `src/db.rs`
This module contains all the logic for interacting with the SQLite database.
### Key Functions
- `create_table`: Creates the full database schema if it does not exist. See `src/db.rs` for the `CREATE TABLE` statements.
- `execute_insert`: A batched, atomic SQL wrapper function that performs multiple insert operations inside a transaction.
- `insert_tx`: Inserts a transaction into `tbl_tx`.
- `insert_inp`: Inserts an input into `tbl_inp`.
- `insert_out`: Inserts an output into `tbl_out`.
- `insert_xpub`: Inserts an xpub into `tbl_xpub`.
- `insert_address`: Inserts a new derived address into `tbl_address`.
- `get_pending_txs`: Queries `tbl_tx` for transactions with `status=0` and valid locktime conditions.
- `update_tx_status`: Updates `status` to `1` (sent) or `2` (failed) after a broadcast attempt.
- `get_stats`: Aggregates statistics for the `tbl_stats` table.
- `get_address_by_ip`: A query that joins `tbl_address` with `tbl_xpub` to find addresses by IP for rate limiting or reuse logic.
### Design Notes
SQL queries are built using `format!` in many places. The `execute_insert` function attempts to batch inserts to reduce transaction overhead, but this is dependent on the SQLite version.
---
## `xpub.rs` (Extended Public Key Utilities)
**Location:** `src/xpub.rs`
This module handles the derivation of Bitcoin addresses from extended public keys (xpub/zpub) and the creation of P2WPKH descriptors.
### Key Functions
- `parse_xpub`: Parses a Base58-encoded xpub/zpub string into a `bitcoin::bip32::Xpub`.
- `derive_address`: Derives a P2WPKH (Bech32) address at a given address index from the xpub. Uses the BIP-84 path (`m/84'/coin_type'/account'/0/index`). Uses `Secp256k1` from the `secp256k1` crate for elliptic curve math.
- `get_descriptor`: Generates a Bitcoin descriptor string for the xpub (e.g., `wpkh(.../0/*)`), which is useful for wallet integration.
- `checksum_verify`: Verifies the Base58 checksum of an xpub/zpub string to prevent data corruption during entry.
### Dependencies
- `bitcoin::bip32::Xpub`
- `secp256k1::Secp256k1`
- `bs58` for Base58 decoding
- `bitcoin::Address::p2wpkh` for address creation
---
## `bal-server.rs` (HTTP Server / API)
**Location:** `src/bin/bal-server.rs`
The main application binary that provides an async HTTP server.
### Architecture
- **Runtime:** `tokio::main` with `rt-multi-thread`.
- **HTTP Framework:** `hyper` (low-level) + `hyper-util` + `http-body-util`. Each connection is spawned as a new `tokio::task`.
- **Routing:** Routes are matched using path regex and a simple match on the HTTP method. The router is implemented manually in `main`.
### Key Routes (implemented in source code)
- `GET /`, `GET /version`: Returns static strings (name and version).
- `GET /.pub_key.pem`: Returns the Ed25519 public key PEM file for signature verification.
- `GET /:network/info`: Returns JSON with fee, address, and chain info. Networks: `bitcoin`, `testnet`, `testnet4`, `signet`, `regtest`.
- `GET /:network/stats`: Returns per-chain statistics if `expose_stats` is enabled.
- `POST /:network/pushtxs`: Accepts one or more raw hex transactions. It validates them, checks the fee output to the `our_address` for that network, and stores the transaction in the database. See `src/bin/bal-server.rs` for the `pushtxs` request body parsing logic.
- `POST /searchtx`: Accepts a txid in the request body and returns the transaction details, status, and fee breakdown.
### Configuration
- The server reads environment variables and/or a config file (`confy`). Default config is hardcoded for `regtest` development.
- `db_file`: The path to the SQLite database (e.g., `bal.db`).
- `bind_address`: The address to listen on (e.g., `127.0.0.1:3031`).
- `expose_stats`: A boolean flag to enable/disable the stats endpoint.
### Error Handling
- **WARNING:** This binary uses `unwrap()` and `expect()` on many critical paths (e.g., `sqlite::open`, `Regex::new`, `req.collect()`). A malformed request could crash the async task or even the entire runtime. This is a known vulnerability.
### Static Public Key (`/.pub_key.pem`)
The server serves a static `public_key.pem` file. The corresponding private key (`privkey.pem`) is used by the pusher to sign statistics before sending them to the `welist` server. This file is located in the project root directory.
---
## `bal-pusher.rs` (Async Transaction Pusher)
**Location:** `src/bin/bal-pusher.rs`
This is the async daemon that monitors the blockchain and pushes pending transactions.
### Architecture
- **Runtime:** `tokio::main`.
- **ZMQ:** `zmq::Context` with a `SUB` socket that listens to `tcp://127.0.0.1:28332` (or similar per-network port). The topic is `hashblock` (32-byte block hash).
- **RPC:** It uses the `bitcoincore-rpc` client to call `getblockchaininfo` (to get the `mediantime`) and `sendrawtransaction` for each transaction.
- **HTTP Client:** `reqwest` with the `json` feature. It sends a signed JSON POST to the `welist` server.
### Key Logic
1. On every `hashblock` message, it calls `main_result()`.
2. `main_result` creates a `bitcoincore-rpc` client. If it fails, it **panics** (`panic!("impossible to get client {}", e)`), crashing the entire process.
3. It fetches `getblockchaininfo` to get the `mediantime`.
4. It queries the database for transactions with `status=0` and `locktime < mediantime`.
5. For each pending transaction, it calls `sendrawtransaction`.
6. If `send_stats` is enabled, it collects statistics, signs them with `privkey.pem`, and sends them to the configured `welist` URL via `reqwest`.
7. It updates the database with the new status.
### Configuration
- `zmq_endpoint`: The ZMQ endpoint (e.g., `tcp://127.0.0.1:28332`).
- `rpc_url`: The URL of the Bitcoin RPC (e.g., `http://127.0.0.1:18443`).
- `rpc_auth`: `user_pass` or `cookie_file`. The cookie path is constructed from the `HOME` environment variable (e.g., `~/.bitcoin/.cookie`).
- `send_stats`: A boolean that enables the remote server reporting.
- `welist_url`: The URL to POST to.
- `ssl_key_path`: The path to the Ed25519 private key (`privkey.pem`) for signing stats.
---
## `bal-pusher-enhanced.rs` (Synchronous Transaction Pusher)
**Location:** `src/bin/bal-pusher-enhanced.rs`
This is a synchronous variant of the pusher that does not rely on the RPC for getting the `mediantime`.
### Architecture (Synchronous)
- **ZMQ:** It uses `zmq::Context` with a `SUB` socket but does not use `zmq` in an async context. It calls `recv_multipart(0)` in a blocking loop (`std::thread::sleep`).
- **Topic:** `rawblock` (not `hashblock`).
- **Block Header:** It extracts the first 80 bytes (the header) from the raw block. The `timestamp` field is at byte offset 4 + 32 + 32 = 68, and is 4 bytes long (little-endian). It uses `byteorder` to read this. This avoids the `getblockchaininfo` RPC call.
- **Block Median Time:** It computes the rolling median time from the timestamps of the last 1000 blocks.
- **Preload:** It fetches and sorts the pending transactions from the database at startup, keeping them in memory. This reduces the database round trip.
- **RPC:** `sendrawtransaction` is used for the pending transactions, but not for `getblockchaininfo`.
### Design Notes
- The ZMQ socket is blocking and has no timeout. If the Bitcoin node stops sending, the thread will hang indefinitely. The sleep between attempts is `std::thread::sleep(Duration::from_secs(1))`, but this happens *after* a successful `recv_multipart`, not if `recv` blocks. This is a potential DoS vector if the ZMQ connection goes silent.
- The `main_result` function is not async and does not use a `tokio` runtime.