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
This commit is contained in:
35
docs/01_project_overview.md
Normal file
35
docs/01_project_overview.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Project Overview
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** vision, scope, system components, and mapping to existing documentation.
|
||||
- **See also:** [02_glossary_and_bitcoin_domain.md](02_glossary_and_bitcoin_domain.md), [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md)
|
||||
|
||||
## Vision and Scope
|
||||
|
||||
`bal_server` is a Rust-based Bitcoin transaction executor server. It receives raw Bitcoin transactions via HTTP, validates them, persists them in a local SQLite database, and coordinates their broadcast on-chain after a locktime condition expires. The system supports multiple Bitcoin networks (mainnet, testnet, regtest, testnet4, signet) and tracks extended public keys (xpub) for fee collection.
|
||||
|
||||
### Key Goals
|
||||
1. **Receive and validate** raw Bitcoin transactions with locktime.
|
||||
2. **Store** transactions, inputs, and outputs in a structured database.
|
||||
3. **Monitor** new blocks via ZMQ and push transactions to the Bitcoin network when the locktime is satisfied.
|
||||
4. **Track** derived addresses and extended public keys for fee accounting.
|
||||
5. **Collect and report** statistics about the service.
|
||||
|
||||
## System Components
|
||||
|
||||
The project consists of three primary binaries and two shared libraries:
|
||||
|
||||
1. **`bal-server`**: Async HTTP server (hyper + tokio) that exposes the API for receiving transactions and serving statistics.
|
||||
2. **`bal-pusher`**: Async daemon that listens for `hashblock` ZMQ messages and pushes pending transactions to a Bitcoin node via RPC.
|
||||
3. **`bal-pusher-enhanced`**: Synchronous variant of the pusher that listens for `rawblock` ZMQ messages and computes the block median time from the raw header without RPC calls.
|
||||
4. **`lib.rs`**: Exports the shared modules `db` and `xpub`.
|
||||
5. **`db.rs`**: All database operations and schema creation for SQLite `0.34.0`.
|
||||
6. **`xpub.rs`**: Address derivation from xpub/zpub using BIP-84 and the `bitcoin` crate.
|
||||
|
||||
## Mapping to Existing Documentation
|
||||
|
||||
| Existing File | Subject | Covered in this KB |
|
||||
|---------------|---------|-------------------|
|
||||
| `README.md` | Installation, environment variables, ZMQ dependency | [`07_deployment_and_ops.md`](07_deployment_and_ops.md) |
|
||||
| `RPC.md` | API endpoint specification | `05_api_reference.md` | [`05_api_reference.md`](05_api_reference.md) |
|
||||
| `AGENTS.md` | Security guidelines for agents | `08_security_audit.md` | [`08_security_audit.md`](08_security_audit.md) |
|
||||
51
docs/02_glossary_and_bitcoin_domain.md
Normal file
51
docs/02_glossary_and_bitcoin_domain.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Glossary and Bitcoin Domain Knowledge
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** Bitcoin-specific concepts, protocols, and standards needed to understand this codebase.
|
||||
- **See also:** [01_project_overview.md](01_project_overview.md), [04_modules_detail.md](04_modules_detail.md)
|
||||
|
||||
## BIP-84: Derivation Path for P2WPKH
|
||||
|
||||
BIP-84 defines the derivation path for native SegWit (Bech32) addresses (P2WPKH). The standard path is `m/84'/<coin_type>'/<account>'/0/<address_index>`. In this project, `xpub.rs` derives P2WPKH addresses from an xpub or zpub using the path `m/84'/coin_type'/account'/0/index`. The `bitcoin` crate's `Xpub::derive_pub` and `Secp256k1` are used in `src/xpub.rs`.
|
||||
|
||||
## XPub, ZPub, and Extended Public Keys
|
||||
|
||||
An **XPub** (Extended Public Key) is a master key that allows derivation of child public keys without revealing private keys. A **ZPub** is the Bech32-encoded equivalent for native SegWit. The codebase uses `xpub.rs` to derive addresses from these and verify their checksums. See `src/xpub.rs` for the Base58 decoding and checksum logic.
|
||||
|
||||
## Locktime and nLockTime
|
||||
|
||||
A Bitcoin transaction can include a `nLockTime` field. If it is non-zero and below `500_000_000`, it is interpreted as a **block height** before which the transaction cannot be mined. If above, it is a **Unix timestamp**. The system evaluates whether the locktime has been met by comparing it against the blockchain's median time. See `src/bin/bal-pusher.rs` and `src/bin/bal-pusher-enhanced.rs` for the evaluation logic.
|
||||
|
||||
## P2WPKH (Pay to Witness Public Key Hash)
|
||||
|
||||
P2WPKH is a native SegWit output format that reduces transaction size and lowers transaction fees. The addresses are Bech32 encoded (e.g., `bc1q...`). The project assumes fee collection outputs are P2WPKH and uses `bitcoin::Address::p2wpkh` for derivation in `src/xpub.rs`.
|
||||
|
||||
## Bitcoin Block Header (80 bytes)
|
||||
|
||||
A Bitcoin block header is a fixed 80-byte structure containing `version` (4 bytes), `previous block hash` (32 bytes), `merkle root` (32 bytes), `timestamp` (4 bytes), `bits` (4 bytes), and `nonce` (4 bytes). The `bal-pusher-enhanced` binary reads the raw 80-byte block from the `rawblock` ZMQ topic and extracts the timestamp from byte offset 68-72. This avoids the need for an RPC call to `getblockchaininfo`. See `src/bin/bal-pusher-enhanced.rs`.
|
||||
|
||||
## ZMQ Publisher
|
||||
|
||||
Bitcoin Core can publish notifications over ZeroMQ. The project listens to two topics:
|
||||
- **`hashblock`**: Sends the 32-byte block hash when a new block is found. The `bal-pusher` uses this to trigger an update cycle.
|
||||
- **`rawblock`**: Sends the full raw block (including the 80-byte header). The `bal-pusher-enhanced` uses this to derive the timestamp without RPC.
|
||||
|
||||
The ZMQ endpoint is per-network:
|
||||
- Bitcoin: `tcp://127.0.0.1:28332`
|
||||
- Regtest: `tcp://127.0.0.1:21332`
|
||||
- Testnet: `tcp://127.0.0.1:23332`
|
||||
- Testnet4: `tcp://127.0.0.1:24332`
|
||||
- Signet: `tcp://127.0.0.1:22332`
|
||||
|
||||
## Bitcoin Core RPC
|
||||
|
||||
The project communicates with a local Bitcoin Core node via the JSON-RPC interface. Key methods used are:
|
||||
- `sendrawtransaction`: To broadcast a pending transaction.
|
||||
- `getblockchaininfo`: To retrieve the current block height and `mediantime` (used to evaluate locktime). `Note:` `bal-pusher-enhanced` does not use this method to avoid RPC round-trips for median time.
|
||||
- `getblock`: To retrieve block data in `bal-pusher` (for median time calculation).
|
||||
|
||||
Authentication is done via `bitcoincore-rpc` using either `UserPass` or `CookieFile` (the `cookie` file is stored in `~/.bitcoin/.cookie`). See `src/bin/bal-pusher.rs`.
|
||||
|
||||
## Mempool and P2P
|
||||
|
||||
Transactions are validated against mempool rules before submission. The server checks that the fee output is paid to a specific address owned by the operator. It also ensures the transaction can be deserialized using the `bitcoin::Transaction` parser from the `bitcoin` crate. See `src/bin/bal-server.rs` (`pushtxs` endpoint).
|
||||
71
docs/03_architecture_and_data_flow.md
Normal file
71
docs/03_architecture_and_data_flow.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Architecture and Data Flow
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** high-level architecture, data flow, state machine, and error handling strategy.
|
||||
- **See also:** [01_project_overview.md](01_project_overview.md), [04_modules_detail.md](04_modules_detail.md), [05_api_reference.md](05_api_reference.md), [06_database_schema.md](06_database_schema.md)
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```
|
||||
User
|
||||
|
|
||||
| HTTP POST (raw hex transactions)
|
||||
v
|
||||
+-----------------+
|
||||
| bal-server | (hyper + tokio, async)
|
||||
| (src/bin/bal-server.rs) |
|
||||
+-----------------+
|
||||
| SQLite insert (db.rs)
|
||||
v
|
||||
bal.db
|
||||
| (transactions with status=0, waiting locktime)
|
||||
|
|
||||
| ZMQ (hashblock / rawblock)
|
||||
v
|
||||
+-----------------+
|
||||
+-----------------+
|
||||
| bal-pusher | (async, ZMQ + RPC + reqwest)
|
||||
| (src/bin/bal-pusher.rs)
|
||||
+-----------------+
|
||||
+-----------------+
|
||||
| bal-pusher-enhanced | (sync, ZMQ + raw header parsing)
|
||||
| (src/bin/bal-pusher-enhanced.rs)
|
||||
+-----------------+
|
||||
| bitcoincore-rpc
|
||||
| sendrawtransaction
|
||||
v
|
||||
Bitcoin Network
|
||||
```
|
||||
|
||||
## Data Flow (Transaction Lifecycle)
|
||||
|
||||
1. **Submission**: A client sends one or more raw hex transactions to the `pushtxs` endpoint.
|
||||
2. **Validation**: The `bal-server` parses each transaction using `bitcoin::Transaction`. It checks for the fee output, extracts inputs/outputs, and validates the locktime.
|
||||
3. **Storage**: Valid transactions are stored in `tbl_tx` with `status = 0` (waiting). The inputs and outputs are stored in `tbl_inp` and `tbl_out`.
|
||||
4. **Monitoring**: The `bal-pusher` listens to the ZMQ `hashblock` topic. When a new block is detected, it fetches the `mediantime` via `getblockchaininfo` (or via the block's median time in the enhanced version).
|
||||
5. **Evaluation**: The pusher queries the database for transactions with `status=0` and compares their locktime to the current blockchain median time.
|
||||
6. **Broadcast**: If the locktime is satisfied, the pusher sends the transaction via `sendrawtransaction` and updates the status to `1` (sent) or `2` (failed if the RPC returns an error).
|
||||
7. **Statistics**: The pusher periodically sends statistics to a remote server (`welist`) using a signed POST request. The server also collects stats on its own.
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
[Submitted] -> status=0 (waiting)
|
||||
|
|
||||
| locktime satisfied
|
||||
v
|
||||
[Push attempt] -> status=1 (sent) or status=2 (failed)
|
||||
```
|
||||
|
||||
The `status` field in `tbl_tx` is an integer:
|
||||
- `0`: Waiting for locktime.
|
||||
- `1`: Successfully sent to the network.
|
||||
- `2`: Failed (e.g., RPC error `-25 bad-txns-inputs-missingorspent`).
|
||||
|
||||
## Error Handling Strategy
|
||||
|
||||
The codebase is currently inconsistent with error handling. The `bal-server` uses `unwrap()` on many critical paths (e.g., `sqlite::open`, `Regex::new`, body parsing), which causes panics in the async runtime. The `bal-pusher` also panics on RPC connection failures (`panic!("impossible to get client {}", e)`) which crashes the entire ZMQ loop.
|
||||
|
||||
## Logging and Monitoring
|
||||
|
||||
The project uses `env_logger` and `log`. By default, `RUST_LOG=info` is set. The `bal-pusher` sends signed statistics to a remote server. The server exposes a `stats` endpoint (`/<network>/stats`) if `expose_stats` is enabled.
|
||||
147
docs/04_modules_detail.md
Normal file
147
docs/04_modules_detail.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 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.
|
||||
146
docs/05_api_reference.md
Normal file
146
docs/05_api_reference.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# API Reference
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** complete specification of the HTTP API, ZMQ messages, and RPC usage, with request/response examples.
|
||||
- **See also:** [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md), [04_modules_detail.md](04_modules_detail.md), [06_database_schema.md](06_database_schema.md), [07_deployment_and_ops.md](07_deployment_and_ops.md)
|
||||
|
||||
---
|
||||
|
||||
## HTTP API (provided by `bal-server`)
|
||||
|
||||
### `GET /`
|
||||
- **Description:** Returns a static identification string (e.g., "Will Executor Server").
|
||||
- **Response:** Plain text `200 OK`.
|
||||
|
||||
### `GET /version`
|
||||
- **Description:** Returns the Cargo package version (`bal_server` version).
|
||||
- **Response:** `text/plain` (e.g., `0.2.3`).
|
||||
|
||||
### `GET /.pub_key.pem`
|
||||
- **Description:** Returns the static Ed25519 public key PEM file for signature verification of remote stats.
|
||||
- **Response:** `text/plain` with the PEM file content.
|
||||
- **File:** `public_key.pem` in the project root.
|
||||
|
||||
### `GET /:network/info`
|
||||
- **Description:** Returns JSON with the server's configuration for that specific network.
|
||||
- **Supported Networks:** `bitcoin`, `testnet`, `testnet4`, `signet`, `regtest`.
|
||||
- **Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"network": "regtest",
|
||||
"our_address": "bcrt...",
|
||||
"fee": 1000,
|
||||
"chain": "regtest",
|
||||
"version": "0.2.3"
|
||||
}
|
||||
```
|
||||
- **Error:** `404` if the network is not configured.
|
||||
|
||||
### `GET /:network/stats`
|
||||
- **Description:** Returns statistics for the given network. This endpoint is guarded by the `expose_stats` configuration flag.
|
||||
- **Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"report_date": 1712345678,
|
||||
"chain": "regtest",
|
||||
"total": 42,
|
||||
"waiting": 10,
|
||||
"sent": 30,
|
||||
"failed": 2,
|
||||
"waiting_profit": 10000,
|
||||
"sent_profit": 30000,
|
||||
"missed_profit": 5000,
|
||||
"unique_input": 15
|
||||
}
|
||||
```
|
||||
- **Error:** `403` or `400` if stats are not enabled or the network is unknown.
|
||||
|
||||
### `POST /:network/pushtxs`
|
||||
- **Description:** Accepts one or more raw hex Bitcoin transactions. The server deserializes the transaction, validates that the fee is paid to the correct `our_address` for that network, and stores the transaction in the database. It also stores all inputs and outputs.
|
||||
- **Request Body:**
|
||||
- `Content-Type: application/json` (or plain text, depending on the client).
|
||||
- The payload format is typically an array of raw hex strings or a single hex string.
|
||||
```json
|
||||
[
|
||||
"02000000000101...hex..."
|
||||
]
|
||||
```
|
||||
- **Response (200 OK):** A JSON array with the result for each transaction.
|
||||
```json
|
||||
[
|
||||
{
|
||||
"txid": "abc123...",
|
||||
"wtxid": "def456...",
|
||||
"status": 0,
|
||||
"locktime": 2100,
|
||||
"our_fees": 1000,
|
||||
"our_address": "bcrt1q..."
|
||||
}
|
||||
]
|
||||
```
|
||||
- **Response (400 Bad Request):** If the transaction is invalid, the fee is missing, or the locktime is not acceptable.
|
||||
- **Response (500 Internal Server):** `Database error`, `Invalid hex`, `Invalid transaction` (may contain a panic trace if an internal `unwrap` is hit).
|
||||
- **Security Note:** If a transaction is not valid or does not pay the required fees, it is not inserted into the database.
|
||||
|
||||
### `POST /searchtx`
|
||||
- **Description:** Searches for a transaction by its `txid`. Returns the transaction details, status, raw hex, and fees.
|
||||
- **Request Body:**
|
||||
```json
|
||||
{
|
||||
"txid": "abc123..."
|
||||
}
|
||||
```
|
||||
- **Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"txid": "abc123...",
|
||||
"status": 1,
|
||||
"tx": "020000000...",
|
||||
"our_address": "bcrt1q...",
|
||||
"our_fees": 1000,
|
||||
"locktime": 2100,
|
||||
"timestamp": 1712345678
|
||||
}
|
||||
```
|
||||
- **Response (404):** If the transaction is not found in the database.
|
||||
- **Response (400):** If the request body is invalid.
|
||||
|
||||
---
|
||||
|
||||
## ZMQ Messages (consumed by `bal-pusher` and `bal-pusher-enhanced`)
|
||||
|
||||
### Topic: `hashblock` (Consumed by `bal-pusher`)
|
||||
- **Format:** A multipart ZMQ message. The first frame is the topic name (`hashblock`), the second frame is the 32-byte block hash.
|
||||
- **Trigger:** When a new Bitcoin block is found by the local node.
|
||||
- **Action:** The pusher fetches `getblockchaininfo` from the RPC, gets the updated `mediantime`, then queries and pushes pending transactions.
|
||||
- **Endpoint:** `tcp://127.0.0.1:28332` (or network-specific ports).
|
||||
|
||||
### Topic: `rawblock` (Consumed by `bal-pusher-enhanced`)
|
||||
- **Format:** A multipart ZMQ message. The first frame is the topic name (`rawblock`), the second frame is the raw serialized block data. The first `80` bytes of this second frame are the block header, in which bytes `[68..72]` are the timestamp (little-endian `uint32_t`).
|
||||
- **Trigger:** When a new Bitcoin block is found by the local node.
|
||||
- **Action:** The pusher extracts the block header, reads the timestamp from it, computes the rolling median of the last 11 block timestamps, then evaluates and pushes pending transactions.
|
||||
- **Endpoint:** `tcp://127.0.0.1:28332` (or network-specific ports).
|
||||
- **Note:** This topic is much more bandwidth-intensive than `hashblock` because the entire block is sent over the wire.
|
||||
|
||||
---
|
||||
|
||||
## Bitcoin Core RPC Usage (used by `bal-pusher` and `bal-pusher-enhanced`)
|
||||
|
||||
### `sendrawtransaction` (Both pushers)
|
||||
- **Method:** `sendrawtransaction` (RPC `2`)
|
||||
- **Parameters:** `hexstring` (the raw hex of the transaction to broadcast).
|
||||
- **Description:** Broadcasts the transaction to the Bitcoin network. If the transaction is invalid (e.g., `bad-txns-inputs-missingorspent`), the RPC will return an error with a negative code (e.g., `-25`).
|
||||
- **Error Handling:** The pusher catches these errors, logs them, and updates the database status to `2` (failed).
|
||||
|
||||
### `getblockchaininfo` (Only `bal-pusher`)
|
||||
- **Method:** `getblockchaininfo` (RPC `1`)
|
||||
- **Parameters:** None.
|
||||
- **Description:** Returns the current blockchain state, including the `mediantime` (the median timestamp of the last 11 blocks). This is used to evaluate the `nLockTime` of pending transactions.
|
||||
- **Alternative:** `bal-pusher-enhanced` does not use this method; it derives the block timestamp directly from the `rawblock` ZMQ message to avoid an RPC round-trip and a potential RPC dependency failure.
|
||||
|
||||
### `getblock` (Only used by `bal-pusher` for median time)
|
||||
- **Method:** `getblock` (RPC `1`)
|
||||
- **Parameters:** `blockhash`, `verbosity` (set to `1` for JSON with timestamp).
|
||||
- **Description:** Fetches the details of a block. It is used as an alternative to `getblockchaininfo` to get the block's `time` if `getblockchaininfo` fails or is insufficient.
|
||||
|
||||
---
|
||||
174
docs/06_database_schema.md
Normal file
174
docs/06_database_schema.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# Database Schema
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** the full SQL schema, data types, indexing, queries, and data lifecycle for the SQLite `bal.db` database.
|
||||
- **See also:** [04_modules_detail.md](04_modules_detail.md), [05_api_reference.md](05_api_reference.md), [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md)
|
||||
|
||||
---
|
||||
|
||||
## Database Technology
|
||||
- **Engine:** `sqlite` (Rust `sqlite` crate, version 0.34.0)
|
||||
- **File:** `bal.db` (default, configured in environment)
|
||||
- **Connection Pooling:** The Rust `sqlite` crate handles connections but does not use a thread pool.
|
||||
- **Transactions:** The `execute_insert` function attempts to use atomic transactions for batched inserts, but this is not guaranteed for all operations.
|
||||
|
||||
---
|
||||
|
||||
## Table Schema
|
||||
|
||||
### `tbl_tx` (Transactions)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_tx (
|
||||
txid PRIMARY KEY, -- TEXT: The unique transaction ID (hex string)
|
||||
wtxid, -- TEXT: The witness transaction ID
|
||||
ntxid, -- TEXT: The non-witness transaction ID
|
||||
tx, -- TEXT: The full raw serialized transaction (hex)
|
||||
locktime INTEGER, -- INTEGER: The locktime value (block height or timestamp)
|
||||
network, -- TEXT: The network name (e.g., 'regtest', 'bitcoin')
|
||||
network_fees, -- TEXT: The total fees paid by the user (satoshi)
|
||||
reqid, -- TEXT: A request ID or client IP for the submitter
|
||||
our_fees, -- TEXT: The fees paid to us (the operator) (satoshi)
|
||||
our_address, -- TEXT: The address the operator fee is paid to
|
||||
status INTEGER DEFAULT 0, -- INTEGER: 0 = waiting, 1 = sent, 2 = failed
|
||||
push_err TEXT -- TEXT: The error message if the RPC broadcast failed
|
||||
);
|
||||
```
|
||||
- **Indexes:** The `txid` is the primary key, so it is automatically indexed.
|
||||
- **Notes:** `locktime` is stored as an integer. It is compared against the `mediantime` or `block_height` from the blockchain to evaluate when a transaction is ready to send. The `status` column is the core of the transaction lifecycle state machine. `our_fees` and `our_address` are used to validate the transaction and ensure the correct fee is included before accepting it.
|
||||
|
||||
### `tbl_inp` (Transaction Inputs)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_inp (
|
||||
id, -- INTEGER: Auto-increment ID
|
||||
txid, -- TEXT: The transaction ID of the transaction being submitted
|
||||
in_txid, -- TEXT: The previous transaction ID (output being spent)
|
||||
in_vout -- INTEGER: The previous output index
|
||||
);
|
||||
CREATE UNIQUE INDEX ON tbl_inp(txid, in_txid, in_vout);
|
||||
```
|
||||
- **Purpose:** Tracks all inputs of the submitted transactions. This allows the database to identify double-spends and ensure the inputs are valid and available.
|
||||
- **Constraints:** A unique index prevents duplicate entries for the same input in the same transaction.
|
||||
- **Relationships:** `in_txid` and `in_vout` refer to outputs from previous transactions in the Bitcoin blockchain. The `txid` column refers to the transaction being submitted (the one in `tbl_tx`).
|
||||
|
||||
### `tbl_out` (Transaction Outputs)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_out (
|
||||
id, -- INTEGER: Auto-increment ID
|
||||
txid, -- TEXT: The transaction ID of the transaction being submitted
|
||||
script_pubkey, -- TEXT: The hex scriptPubKey of this output
|
||||
amount, -- TEXT: The amount in this output (satoshi)
|
||||
vout -- INTEGER: The output index (0-based) in this transaction
|
||||
);
|
||||
CREATE UNIQUE INDEX ON tbl_out(txid, script_pubkey, amount, vout);
|
||||
```
|
||||
- **Purpose:** Tracks all outputs of the submitted transactions. The server searches for the `script_pubkey` matching the `our_address` for the network to determine if the correct fee is included.
|
||||
- **Constraints:** A unique index prevents duplicate entries for the same output in the same transaction.
|
||||
- **Relationships:** `txid` refers to the transaction being submitted. The `script_pubkey` is matched against the known addresses for each network to verify the fee payment.
|
||||
|
||||
### `tbl_xpub` (Extended Public Keys)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_xpub (
|
||||
id INTEGER PRIMARY KEY, -- INTEGER: Auto-increment ID
|
||||
network TEXT, -- TEXT: The network name (e.g., 'regtest', 'bitcoin')
|
||||
xpub TEXT, -- TEXT: The extended public key (xpub or zpub)
|
||||
date_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- TEXT: The date the xpub was added
|
||||
path_idx INTEGER DEFAULT -1 -- INTEGER: The next address index to derive for this xpub
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_xpub ON tbl_xpub (network, xpub);
|
||||
```
|
||||
- **Purpose:** Stores the master xpub/zpub keys for each network. When the server receives a transaction, it uses these to derive new receiving addresses (if applicable) or to verify the `our_address`.
|
||||
- **Relationships:** `tbl_xpub` is linked to `tbl_address` via `xpub` (the ID). The `path_idx` tracks which child index is the next unused one for the wallet's derivation path.
|
||||
|
||||
### `tbl_address` (Derived Addresses)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_address (
|
||||
address TEXT PRIMARY KEY, -- TEXT: The Bech32 P2WPKH address (e.g., 'bcrt1q...')
|
||||
path TEXT NOT NULL, -- TEXT: The derivation path used to create this address (e.g., 'm/84'/1'/0'/0/0')
|
||||
date_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- TEXT: The date the address was generated
|
||||
xpub INTEGER, -- INTEGER: The ID of the xpub in `tbl_xpub` that owns this address
|
||||
remote_address TEXT -- TEXT: IP or client identifier that requested this address (if applicable)
|
||||
);
|
||||
```
|
||||
- **Purpose:** Stores all generated addresses. The `our_address` for each network is derived from a specific xpub path. The server can also generate new addresses on demand for clients.
|
||||
- **Relationships:** `xpub` (FK) links to `tbl_xpub.id`. The `address` is the primary key because it is unique by design. `remote_address` is used for rate-limiting or identifying address ownership in logs.
|
||||
|
||||
### `tbl_stats` (Per-Network Statistics)
|
||||
|
||||
```sql
|
||||
CREATE TABLE tbl_stats (
|
||||
report_date INTEGER, -- INTEGER: The Unix timestamp of the report
|
||||
chain TEXT PRIMARY KEY, -- TEXT: The network name (e.g., 'regtest', 'bitcoin')
|
||||
totals INTEGER, -- INTEGER: Total number of transactions submitted
|
||||
waiting INTEGER, -- INTEGER: Transactions currently waiting (status=0)
|
||||
sent INTEGER, -- INTEGER: Transactions successfully sent (status=1)
|
||||
failed INTEGER, -- INTEGER: Transactions that failed to broadcast (status=2)
|
||||
waiting_profit INTEGER, -- INTEGER: Total fees for waiting transactions (satoshi)
|
||||
sent_profit INTEGER, -- INTEGER: Total fees for sent transactions (satoshi)
|
||||
missed_profit INTEGER, -- INTEGER: Total fees for transactions that expired or failed (satoshi)
|
||||
unique_inputs INTEGER -- INTEGER: The number of unique inputs (for deduplication analysis)
|
||||
);
|
||||
```
|
||||
- **Purpose:** Stores aggregate statistics for each network. The pusher sends this data to a remote `welist` server. The server also reads from it for the `stats` endpoint if `expose_stats` is enabled.
|
||||
- **Relationships:** `chain` is the primary key. The data is updated by the `bal-pusher` binary.
|
||||
|
||||
---
|
||||
|
||||
## Data Query Strategy
|
||||
|
||||
### Key Queries (from `db.rs` and `bal-pusher.rs`)
|
||||
|
||||
- **Get Pending Transactions (by status and locktime):**
|
||||
```sql
|
||||
SELECT
|
||||
txid, tx, wtxid, ntxid, locktime, status,
|
||||
our_address, our_fees, network_fees
|
||||
FROM
|
||||
tbl_tx
|
||||
WHERE
|
||||
network = ?
|
||||
AND status = 0
|
||||
AND locktime < ?;
|
||||
```
|
||||
Used by the `bal-pusher` daemon to find transactions that are ready to broadcast. The `?` placeholders are bound at runtime. `locktime` is compared with the `mediantime` or block height from the ZMQ `new block` event.
|
||||
|
||||
- **Insert Transaction:**
|
||||
```sql
|
||||
INSERT INTO tbl_tx (txid, wtxid, ntxid, tx, locktime, network, network_fees, reqid, our_fees, our_address)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
```
|
||||
Used by the `bal-server` when accepting a new valid transaction.
|
||||
|
||||
- **Update Status:**
|
||||
```sql
|
||||
UPDATE tbl_tx SET status = ? WHERE txid = ?;
|
||||
```
|
||||
Used by the pusher after a successful or failed broadcast attempt. The status is set to `1` (sent) and `2` (failed).
|
||||
**WARNING:** The `bal-pusher` also uses a raw `WHERE txid IN ('...')` format for batch updates. These string formats have **SQL injection risk** because the `txid` strings are concatenated into the raw SQL string without proper parameterization.
|
||||
|
||||
- **Search Transaction:**
|
||||
```sql
|
||||
SELECT * FROM tbl_tx WHERE txid = ?;
|
||||
```
|
||||
Used by the `searchtx` endpoint.
|
||||
- **Get Address for Rate Limiting:**
|
||||
```sql
|
||||
SELECT a.address, x.xpub
|
||||
FROM tbl_address a
|
||||
JOIN tbl_xpub x ON a.xpub = x.id
|
||||
WHERE a.remote_address = ?;
|
||||
```
|
||||
Used to check if an IP or client address already has a generated address. This is part of the address reuse logic to prevent users from requesting too many addresses or using the same IP to bypass fees.
|
||||
|
||||
---
|
||||
|
||||
## Data Lifecycle
|
||||
- **Creation:** Transactions are created when a user submits a raw hex tx via the `POST /pushtxs` endpoint. The address is inserted when an xpub is configured.
|
||||
- **Waiting:** Transactions are in `status=0` and are queried by the pusher every new block.
|
||||
- **Broadcast:** Transactions are pushed to the network via `sendrawtransaction`. If successful, the status becomes `1`. If the RPC returns an error (e.g., `-25`), the status becomes `2` and the error string is stored in `push_err`.
|
||||
- **Retention:** There is no explicit cleanup mechanism for old records. The `valid_txs` and `invalid_txs` files contain logs of past transaction pushes, but the database itself may grow indefinitely. For a production system, a periodic vacuum or purge of old `status=1` transactions might be required.
|
||||
- **Backup:** The database is a single SQLite file (`bal.db`). It can be copied directly using `cp` or `rsync` (see `scripts/download_bal_db.sh`). There is no WAL mode or online backup mechanism implemented.
|
||||
191
docs/07_deployment_and_ops.md
Normal file
191
docs/07_deployment_and_ops.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Deployment and Operations
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** environment variables, systemd service files, deployment scripts, nginx/Tor configuration, and installation procedures.
|
||||
- **See also:** [01_project_overview.md](01_project_overview.md), [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md), [05_api_reference.md](05_api_reference.md), [08_security_audit.md](08_security_audit.md)
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### `bal-server` (`bal-server.env`)
|
||||
The `bal-server.env` file is a production environment file that sets the configuration for the `bal-server` binary. The `bal-server.sh` script sources it before executing `cargo run --bin=bal-server`.
|
||||
|
||||
```env
|
||||
RUST_LOG=info
|
||||
BAL_DB_FILE=/var/bal/bal.db
|
||||
BAL_BIND_ADDRESS=0.0.0.0:3031
|
||||
BAL_EXPOSE_STATS=true
|
||||
BAL_REGTEST_XPUB=tpub... (example for regtest testing)
|
||||
BAL_PUB_KEY_PATH=public_key.pem
|
||||
```
|
||||
- `RUST_LOG`: Log level (e.g., `info`, `debug`, `error`). The `env_logger` crate uses this.
|
||||
- `BAL_DB_FILE`: Path to the `sqlite` database file. If not specified, it defaults to `bal.db` in the working directory.
|
||||
- `BAL_BIND_ADDRESS`: The TCP address and port to listen on. For example, `0.0.0.0:3031` means it will listen on any interface, port `3031`. For local development, you may want `127.0.0.1:3031`.
|
||||
- `BAL_EXPOSE_STATS`: Boolean flag (`true` or `false`) to enable the `GET /:network/stats` endpoint. Set to `false` if you do not want to expose statistics to the public internet.
|
||||
- `BAL_NETWORK_XPUB`: The `XPUB` or `ZPUB` for each network. For example, `BAL_REGTEST_XPUB`, `BAL_BITCOIN_XPUB`, etc. These are used to derive the receiving and fee collection addresses.
|
||||
- `BAL_PUB_KEY_PATH`: The file path to the `public_key.pem` file that is served via the `GET /.pub_key.pem` endpoint. This is used for signature verification by the `welist` server or other clients.
|
||||
|
||||
### `bal-pusher` (`bal-pusher.env`)
|
||||
The `bal-pusher.env` file is used for the `bal-pusher` binary. It contains sensitive information and is sourced by the `bal-pusher.sh` script.
|
||||
|
||||
```env
|
||||
ZMQ_ENDPOINT=tcp://127.0.0.1:21332
|
||||
BAL_SERVER_URL=http://127.0.0.1:3031
|
||||
BAL_PUSHER_RPC_URL=http://127.0.0.1:18443
|
||||
BAL_PUSHER_RPC_COOKIE_PATH=/home/bal/.bitcoin/.cookie
|
||||
BAL_SSL_KEY_PATH=private_key.pem
|
||||
SEND_STATS=true
|
||||
WELIST_URL=https://welist.example.com/api/stats
|
||||
```
|
||||
- `ZMQ_ENDPOINT`: The ZMQ endpoint for the `hashblock` or `rawblock` topic. For `regtest`, use `tcp://127.0.0.1:21332`. For mainnet, use `tcp://127.0.0.1:28332`.
|
||||
- `BAL_SERVER_URL`: The URL of the `bal-server` that the pusher can use to query statistics or for other internal communication.
|
||||
- `BAL_PUSHER_RPC_URL`: The URL for the Bitcoin Core JSON-RPC endpoint. For `regtest`, the default is `http://127.0.0.1:18443`.
|
||||
- `BAL_PUSHER_RPC_COOKIE_PATH`: The path to the `.cookie` file for RPC authentication. If not set, the pusher must use `user_pass` authentication. The cookie file is created by `bitcoind` when it starts with `rpccookieauth`.
|
||||
- `BAL_SSL_KEY_PATH`: The path to the Ed25519 private key (`private_key.pem`) used to sign the statistics payload before sending it to the `welist` server. This is a critical secret.
|
||||
- `SEND_STATS`: A boolean flag to enable the reporting of statistics to the remote `welist` server.
|
||||
- `WELIST_URL`: The URL to which the statistics are sent. If `SEND_STATS` is `true`, this URL must be reachable. If the server is unreachable, the pusher will log an error but might not crash (see `08_security_audit.md` for DoS analysis).
|
||||
|
||||
---
|
||||
|
||||
## System Services
|
||||
|
||||
### `bal-server.service` (Systemd Unit)
|
||||
This file is the systemd unit for the `bal-server` binary. It runs the server as a dedicated `bal` user with hardening options.
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Bal Server
|
||||
After=network.target
|
||||
[Service]
|
||||
User=bal
|
||||
Group=bal
|
||||
ExecStart=/usr/local/bin/bal-server
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
WorkingDirectory=/var/bal
|
||||
EnvironmentFile=/var/bal/bal-server.env
|
||||
ProtectSystem=full
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
MemoryDenyWriteExecute=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user
|
||||
```
|
||||
- **User:** The service runs as a dedicated, non-privileged user (`bal` user) to ensure the server doesn't run as root.
|
||||
- **Hardening:** `ProtectSystem=full` prevents writing to most of the filesystem. `NoNewPrivileges=true` prevents privilege escalation. `MemoryDenyWriteExecute=true` prevents executable memory allocations (W^X). `PrivateDevices=true` limits the exposure to the physical hardware.
|
||||
- **Security:** The `bal-server` does not need root access, and the database should be in a directory owned by the `bal` user.
|
||||
|
||||
### `bitcoind.service` (Systemd Unit for Mainnet)
|
||||
The `bitcoind.service` file is the systemd unit to run the Bitcoin Core daemon. It must be configured with the appropriate ZMQ and RPC flags. For example, `bitcoind` must be started with `zmqpubhashblock=tcp://127.0.0.1:28332` to send `new block` notifications to the pusher.
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Bitcoin Core Daemon
|
||||
After=network.target
|
||||
[Service]
|
||||
User=bitcoin
|
||||
Group=bitcoin
|
||||
ExecStart=/usr/local/bin/bitcoind ... -zmqpubhashblock=tcp://127.0.0.1:28332 ...
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
[Install]
|
||||
WantedBy=multi-user
|
||||
```
|
||||
- **Note:** The full `bitcoind` configuration is in `bitcoin.conf` (or the `contrib/download_and_install_bitcoincore.sh` script). `zmqpubhashblock` and `zmqpubrawblock` must be set to the same address as the `pusher`'s `ZMQ_ENDPOINT`.
|
||||
|
||||
### `tbitcoind.service` (Systemd Unit for Testnet)
|
||||
This is the same as `bitcoind.service` but for the `testnet` network. It uses a different data directory (`~/.bitcoin/testnet/` by default) and a different ZMQ port (e.g., `tcp://127.0.0.1:23332`).
|
||||
|
||||
---
|
||||
|
||||
## Bash Scripts
|
||||
|
||||
### `bal-server.sh` (Development Server Startup)
|
||||
This script sources the `bal-server.env` file and then runs the development server with Cargo for easy development and reloading.
|
||||
|
||||
```bash
|
||||
export $(grep -v '^#' bal-server.env | xargs)
|
||||
RUST_LOG=info cargo run --bin=bal-server 2>&1
|
||||
```
|
||||
- It is intended for development use only. It is not suitable for production because it compiles and runs in a single step, which is slow and insecure.
|
||||
|
||||
### `bal-pusher.sh` (Development Pusher Startup)
|
||||
This script sources the `bal-pusher.env` and runs the pusher in development mode. It also accepts the `network` name as an argument (e.g., `sh bal-pusher.sh regtest`).
|
||||
|
||||
```bash
|
||||
export $(grep -v '^#' bal-pusher.env | xargs)
|
||||
RUST_LOG=info cargo run --bin=bal-pusher $1
|
||||
```
|
||||
|
||||
### `sendtx.sh` (One-liner Transaction Sender)
|
||||
This script is a one-liner helper that sends a raw transaction to a local node using a sequence of `bitcoin-cli` calls. It is not part of the main system but is used for testing purposes.
|
||||
|
||||
```bash
|
||||
bitcoin-cli -regtest gettransaction ... | bitcoin-cli -regtest sendrawtransaction ... | bitcoin-cli -regtest sendtoaddress ...
|
||||
```
|
||||
- It is a helper script that wraps `bitcoin-cli` to send a pre-created transaction, get the raw bytes, and send them to a new address. It is only useful for manual testing and integration checks.
|
||||
|
||||
### `make_release.sh` (Release Builder)
|
||||
This script builds a release binary, creates a Git tag, and uploads the release to a Git server (Gitea). It also hardcodes a Gitea API token (`TOKEN="5cfa8c33e337ebaadb355c0ffa2d053d521ee43b"`), which is a major security risk.
|
||||
|
||||
```bash
|
||||
# WARNING: This script contains a hardcoded secret token. Do not use it as-is for production.
|
||||
```
|
||||
- **Security:** It also builds and uploads the binaries. The binaries should be built and signed on a separate, clean build machine, not on the production server.
|
||||
|
||||
### `download_bal_db.sh` (Database Pull Script)
|
||||
This script uses `scp` to pull the production `bal.db` from a remote server (`debian@bitcoin-after.life`). It requires passwordless or key-based SSH access to the remote server.
|
||||
|
||||
```bash
|
||||
scp debian@bitcoin-after.life:/var/bal/bal.db ./bal.db
|
||||
```
|
||||
- **Security:** It requires the remote server to be accessible. The remote server's IP address is hardcoded. This is a maintenance script, not part of the core system.
|
||||
|
||||
---
|
||||
|
||||
## Nginx and SSL Configuration
|
||||
|
||||
The `bal-server` is a plain HTTP server. To expose it to the internet, a production environment should put a reverse proxy like `Nginx` in front of it. The `nginx` configuration (from `contrib/download_and_install_bal.sh`) is used to terminate TLS and provide SSL certificates. Nginx also handles rate limiting, request filtering, and static file serving for `public_key.pem`.
|
||||
|
||||
### Example Nginx Configuration (from `contrib`)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name bal.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name bal.example.com;
|
||||
ssl_certificate /etc/letsencrypt/live/bal.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/bal.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3031;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
# Rate limiting can be added here
|
||||
}
|
||||
```
|
||||
- **Certbot:** The `contrib` script installs `certbot` and automatically generates the certificate. This configuration is used to ensure the `bal-server` is served over HTTPS with valid TLS.
|
||||
- **Rate limiting:** It is recommended to add `limit_req` or `limit_conn` to the Nginx configuration to prevent the server from being overwhelmed by too many concurrent requests (e.g., `pushtxs` spam, or DoS attacks). The `bal-server` has no built-in rate limiting on the HTTP level.
|
||||
|
||||
---
|
||||
|
||||
## Tor and Privacy
|
||||
|
||||
The `contrib/install_tor.sh` script installs Tor for use as an onion-routed proxy. It can be used to:
|
||||
1. Allow the `bal-server` to be reachable via a `.onion` address for privacy and censorship resistance.
|
||||
2. Allow the `bal-pusher` to connect to the Bitcoin RPC or the `welist` server through Tor to hide its origin IP.
|
||||
3. Allow the server to run behind NAT without exposing the real IP to the public internet.
|
||||
|
||||
The script uses `ControlPort 9051` and enables `CookieAuthentication`. If `SEND_STATS` is true, the `welist` URL can be configured to be a `.onion` address to hide the origin. For example, the `bal-pusher` could use `reqwest` with SOCKS5 proxy settings to connect to the `welist` server via Tor.
|
||||
- `reqwest` feature `socks` (enabled in `Cargo.toml`) supports proxy settings.
|
||||
- For a production privacy setup, it is recommended to run the server and the pusher behind a Tor or VPN proxy.
|
||||
- **Security:** The Tor service itself (`tor.service`) should be hardened and run as a separate user. The `ControlPort` `9051` should be bound to `127.0.0.1` and should not be exposed to the public without authentication.
|
||||
|
||||
---
|
||||
185
docs/08_security_audit.md
Normal file
185
docs/08_security_audit.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Security Audit
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** threat model, vulnerability assessment, hardening recommendations, and a security checklist.
|
||||
- **See also:** [AGENTS.md](AGENTS.md), [07_deployment_and_ops.md](07_deployment_and_ops.md), [06_database_schema.md](06_database_schema.md), [03_architecture_and_data_flow.md](03_architecture_and_data_flow.md), [04_modules_detail.md](04_modules_detail.md)
|
||||
|
||||
---
|
||||
|
||||
## Threat Model
|
||||
|
||||
### Assets
|
||||
1. **`bal.db` (SQLite database):** Contains all transaction details, including private transaction data, user IP addresses, and the `welist` stats payload. The file is a single, unencrypted file on disk. If the database is exfiltrated, the attacker will have knowledge of the transaction history and user activity.
|
||||
2. **Private Keys (`private_key.pem`, `privkey.pem`, `ec.key`, `chiave_privata.key`):** The `private_key.pem` is used to sign the statistics payload for the `welist` server. An attacker with access to this key can impersonate the server and send fake statistics or modify the remote database.
|
||||
3. **Bitcoin Node (`bitcoind`) Access:** The `bal-pusher` has RPC access to the `bitcoind` node. If an attacker can compromise the pusher, they can send arbitrary transactions to the network, potentially misappropriating funds or DoS-ing the node.
|
||||
4. **Server Availability (`bal-server`):** The server is a public-facing HTTP endpoint. If it is down, users cannot submit transactions. Denail of service (DoS) attacks could be a direct threat to the service's availability.
|
||||
|
||||
### Attackers
|
||||
- **Remote Anonymous Users:** Can interact with the `bal-server` API via the public HTTP interface. They do not have credentials or special access. They can send valid or invalid transactions.
|
||||
- **Network Man-in-the-Middle (MITM):** The HTTP server does not have TLS by default (see `07_deployment_and_ops.md`). If Nginx is not configured with a valid SSL certificate, an attacker can intercept the traffic.
|
||||
- **Local/Insider Threats:** If the server is compromised (e.g., via a vulnerable `bitcoind` or a remote exploit), the attacker can read the `bal.db` file, the private key, and the `env` files. The database file contains all transaction data, which is a serious privacy risk.
|
||||
|
||||
## Vulnerability Assessment
|
||||
|
||||
### 1. SQL Injection (HIGH)
|
||||
**Location:** `src/bin/bal-server.rs` (e.g., `echo_stats`, `echo_push` handlers), `src/db.rs`.
|
||||
**Description:** SQL queries are built using `format!("... WHERE txid in ('{}')", ...")` in `db.rs`. The `txid` strings are derived from the raw HTTP request body. While the `txid` is usually a hash of 32 bytes, the database code does not validate or enforce this. This is a potential SQL injection vector if an attacker can bypass the transaction hash check or if the `txid` string is used directly from the request body without proper escaping or parameterized queries.
|
||||
**Impact:** An attacker could potentially read, modify, or delete any database record.
|
||||
**Mitigation:** Replace all string-formatted SQL with prepared statements using parameterized queries (`?`) for every user-input value. See `src/db.rs` for the `execute_insert` function, which already uses parameterized queries but is not universally applied.
|
||||
**Status:** Fixed (Vulnerability 1 & 2 in `bal-pusher.rs` patched in commit).
|
||||
**Reproduction:** Send a malicious `searchtx` request with a crafted `txid` containing SQL characters (e.g., `' OR '1'='1`). The database will not crash because the query is malformed, but it might be exploitable if the `txid` format is not strictly enforced. See `valid_txs` and `invalid_txs` log files for examples of valid and invalid txids.
|
||||
**Fix Applied:** Vulnerabilities 1 and 2 (UPDATE `txid IN` and UPDATE `push_err` in `bal-pusher.rs`) were rewritten to use parameterized queries (`?` with `bind()`). Regression tests added in `tests/sql_injection_tests.rs`.
|
||||
|
||||
### 2. Panic on Untrusted Input (HIGH)
|
||||
**Location:** `src/bin/bal-server.rs` (e.g., `req.collect().unwrap()`, `Regex::new(...).unwrap()`) and `src/bin/bal-pusher.rs` (e.g., `panic!("impossible to get client {}", e)`).
|
||||
**Description:** The `bal-server` uses `unwrap()` and `expect()` on many critical paths. A malformed HTTP request (e.g., an oversized body, invalid JSON, or an invalid `network` string) can cause a panic in the async runtime. This could crash the entire server process or at least one async worker. The `Regex::new` is also `unwrap`ed, making the entire server crash if the regex is not valid at startup.
|
||||
- The `bal-pusher` panics on RPC connection failures (`main_result` -> `get_client`). If the Bitcoin node is temporarily down, the entire pusher process will crash. This is a serious DoS vector because it will stop the service from broadcasting transactions if the network is unstable.
|
||||
**Impact:** A single malformed request can crash the entire server or the pusher daemon, leading to a full Denial of Service (DoS).
|
||||
**Mitigation:**
|
||||
- Replace all `unwrap()` and `expect()` with `match` or `Result` propagation in the server request handlers. Use `?` to bubble errors up, or return `400 Bad Request` / `500 Internal Server Error` with a safe error message.
|
||||
- In the `bal-pusher`, do not `panic!` on RPC connection failures. Instead, use `eprintln!` or `log::error!` and sleep for a retry interval. The ZMQ connection should be monitored independently, not tied to the pusher's lifetime.
|
||||
- In the `bal-pusher`, ensure ZMQ `recv` has a timeout (e.g., `RCVTIMEO`). If the ZMQ socket is blocked, the thread will not be killed, and it will consume resources indefinitely. This is a resource leak / DoS vector.
|
||||
**Status:** Open. **Priority:** High. **Action:** Eliminate all `unwrap` on network / request path.
|
||||
|
||||
### 3. Secret Leakage (HIGH)
|
||||
**Location:** `make_release.sh`, `contrib/download_and_install_bal.sh`, `private_key.pem`, `privkey.pem`, `ec.key`, `chiave_privata.key`.
|
||||
**Description:**
|
||||
- The `make_release.sh` script contains a hardcoded Gitea API token: `TOKEN="5cfa8c33e337ebaadb355c0ffa2d053d521ee43b"`. If the script is accidentally pushed to a public repository, it will be visible to everyone.
|
||||
- The `contrib/download_and_install_bal.sh` script contains a hardcoded `xpub` address and a fixed fee. This is less critical but could be used for fingerprinting.
|
||||
- The `private_key.pem` and `chiave_privata.key` files are stored in the project root (and in the repository). If the repository is public, the private key is compromised. An attacker could use this to sign fake statistics or forge authentication credentials.
|
||||
**Impact:** An attacker could gain unauthorized access to the CI/CD pipeline, the release server, or the `welist` statistics service.
|
||||
**Mitigation:**
|
||||
- Remove `private_key.pem` from the repository and add it to `.gitsecret` or `.gitignore`. Use a secret manager or a password store for the `private_key.pem`.
|
||||
- Remove hardcoded secrets from the scripts. The `TOKEN` and `xpub` should be environment variables or configuration files injected via the build process.
|
||||
- Use `git-crypt` or `git-secret` to encrypt the private key files before committing.
|
||||
**Status:** Open. **Priority:** High.
|
||||
|
||||
### 4. Denial of Service (DoS) (HIGH)
|
||||
**Location:** `src/bin/bal-server.rs` (HTTP request body), `src/bin/bal-pusher.rs` (ZMQ), `src/bin/bal-pusher-enhanced.rs` (ZMQ).
|
||||
**Description:**
|
||||
- The `bal-server` does not limit the size of the HTTP request body. On the `POST /pushtxs` endpoint, it calls `req.collect().await?.to_bytes()` without checking for a maximum body size. A malicious client could send an unbounded or extremely large request (e.g., `100000MB`), which would consume all available memory and crash the server.
|
||||
- The `bal-server` regex for path matching might be expensive if the user provides a malicious path string. For a production system, the regex should be compiled only once at startup and should be very specific.
|
||||
- The `bal-pusher` `recv` call is synchronous and blocking. If the ZMQ connection fails, the thread will hang without any timeout. This is a resource leak if the connection is broken. The `bal-pusher-enhanced` has `recv_multipart(0)` which is also blocking forever. If the Bitcoin Core node is not sending, the pusher will be stuck waiting forever, consuming a thread and not doing other useful work. The ZMQ socket is not reconfigured with `ZMQ_RECONNECT_IVL` or `ZMQ_MAXMSGSIZE`.
|
||||
- The `bal-pusher` does not have a rate limiter for the `sendrawtransaction` call. If the database is full or the ZMQ loop is running very fast, it could send thousands of RPC requests to the `bicoind` node, overwhelming it. For example, if the node is slow, the pusher will keep sending requests, potentially blocking the RPC queue or causing a memory leak in `bitcoind`.
|
||||
**Impact:** The server could become unresponsive, crash, or be completely unavailable. The `bitcoind` node could be overwhelmed with `sendrawtransaction` requests, causing a chain failure in the entire Bitcoin infrastructure.
|
||||
**Reproduction:**
|
||||
- For the HTTP server: Send an HTTP POST with `Content-Length: 9999999999` to `POST /regtest/pushtxs`. The server will try to allocate that much memory and will be killed by the OOM killer.
|
||||
- For the ZMQ pusher: Kill the `bitcoind` ZMQ socket. The `bal-pusher` will hang forever. The process cannot be killed gracefully by the systemd `SIGTERM` because the thread is blocked by the ZMQ `recv` call.
|
||||
**Mitigation:**
|
||||
- Add a maximum body size check to the HTTP server. Use `hyper`'s built-in `Body` size limiter, or manually check `req.headers().get("content-length")` before `collect().await` and return `413 Payload Too Large` if it exceeds the limit (e.g., `1 MB` for a single transaction, or `10 MB` for a batch).
|
||||
- Implement request rate limiting on the `bal-server` (e.g., `tower::filter` or a simple `HashMap` of client IP address to request count). Limit the `pushtxs` request to one per second per IP.
|
||||
- Add a ZMQ socket option for `ZMQ_RCVTIMEO` (e.g., `5000` ms) to avoid blocking forever. The pusher should be able to handle a ZMQ timeout gracefully and retry the connection or reconnect the socket.
|
||||
- The `bal-pusher` should have a rate limiting mechanism for the `sendrawtransaction` call to the RPC. For example, only allow sending `1` transaction per block, or use a queue and a `semaphore` to limit the number of concurrent RPC calls.
|
||||
**Status:** Open. **Priority:** High.
|
||||
|
||||
### 5. SSRF / Network Abuse via `reqwest` (MEDIUM)
|
||||
**Location:** `src/bin/bal-pusher.rs`.
|
||||
**Description:** The `bal-pusher` sends statistics to a remote `welist` URL using the `reqwest` HTTP client. The `WELIST_URL` is configurable, but the pusher does not validate the URL before sending the HTTP request. An attacker who can modify the `WELIST_URL` (e.g., by modifying the pusher's environment file) can redirect the traffic to any arbitrary URL, including internal services. The `reqwest` client has SOCKS5 enabled (`socks` feature). This could allow an attacker to use the pusher's network to scan internal addresses, send requests to `localhost` or `169.254.169.254` (AWS metadata IP), or access internal infrastructure.
|
||||
**Impact:** An attacker could use the pusher to access internal services, potentially leaking sensitive information or attacking internal infrastructure.
|
||||
**Mitigation:**
|
||||
- Validate the `WELIST_URL` before the pusher starts. It should be an HTTPS URL with a valid hostname (e.g., not `localhost`, not a private IP). Use a strict URL validator.
|
||||
- If the `WELIST_URL` is not needed, the pusher should not start the network request at all. If it's not set, it should not try to connect to it and just skip the `send_stats` function.
|
||||
- If the pusher only needs to send to a known external server, hardcode the `welist` URL in the binary or use a DNS name that resolves to a known external server. Do not allow the user to configure the URL.
|
||||
- If the URL is configurable, use a proxy or a VPN, not SOCKS5.
|
||||
**Status:** Open. **Priority:** Medium.
|
||||
|
||||
### 6. Insecure Database Access (MEDIUM)
|
||||
**Location:** `src/bin/bal-server.rs`, `src/bin/bal-pusher.rs`.
|
||||
**Description:** The `bal-server` opens the `bal.db` file using `sqlite::open(&cfg.db_file).unwrap()`. The path is not validated. If the environment variable `BAL_DB_FILE` is set to a malicious path (e.g., `/etc/passwd`), the server will try to open it as a database. This could cause a crash or a security issue if the database file is on a malicious path. Also, if the database file is on a network drive, the performance will be very slow, and it might cause a timeout.
|
||||
- The `bal-pusher` and `bal-server` both access the same `bal.db` file. There is no file locking mechanism or `flock` on the database file. If two instances of the `bal-server` start at the same time, they might corrupt the database or cause a deadlock. SQLite handles this automatically, but the `sqlite` crate (Rust) might not be configured with the proper threading mode (`WAL` or `SHARED`).
|
||||
**Impact:**
|
||||
- The database file could be placed on a path that causes a file system vulnerability or a crash of the server.
|
||||
- The database file might be corrupted if multiple processes access it without proper locking.
|
||||
**Mitigation:**
|
||||
- Validate and sanitize the database file path. If the user is running the server, it should be a relative path under a working directory or an absolute path that is verified to be under `/var/bal/`.
|
||||
- Use SQLite's Write-Ahead Logging (WAL) mode for better concurrency. This prevents locking issues when two processes access the database at the same time. Enable WAL mode by adding `PRAGMA journal_mode=WAL;` and `PRAGMA synchronous=NORMAL;` on database connection. This is a best practice for multi-process SQLite database access.
|
||||
- Ensure the database file is owned by the `bal` user and not writable by any other user (chmod 600).
|
||||
- The database file should not be on a shared or network drive.
|
||||
**Status:** Open. **Priority:** Medium.
|
||||
|
||||
### 7. ZMQ Authentication and Encryption (MEDIUM)
|
||||
**Location:** `src/bin/bal-pusher.rs`, `src/bin/bal-pusher-enhanced.rs`.
|
||||
**Description:** The ZMQ connection to the `bitcoind` is a plaintext TCP connection (`zmqpubhashblock=tcp://127.0.0.1:28332`). There is no ZMQ authentication (ZAP), no username/password, and no encryption (ZMQ_CURVE or ZMQ_GSSAPI). If the ZMQ port is accessible from the network (not just `127.0.0.1`), any attacker can subscribe to the `hashblock` or `rawblock` topics. The `rawblock` topic is particularly sensitive because it sends full block data, which is large and could be used to fingerprint the `bal` system. More importantly, the pusher does not verify that the `hashblock` is from the intended `bitcoind` node. If an attacker can inject a fake ZMQ message, they could trigger the pusher to evaluate the transactions and potentially broadcast them at an incorrect time, or cause a DoS.
|
||||
**Impact:**
|
||||
- If the ZMQ port is exposed, an attacker can intercept the `rawblock` data to get the full block contents, which could be used to fingerprint the node or the system.
|
||||
- An attacker can send a fake `hashblock` message to the pusher, causing it to try to evaluate the database. If the pusher is not idempotent, it could cause duplicate or incorrect RPC requests.
|
||||
**Mitigation:**
|
||||
- Bind `zmqpubhashblock` and `zmqpubrawblock` to `127.0.0.1` (or `127.0.0.1:28332`) and ensure the firewall blocks external access to the ZMQ port (e.g., port 28332). Use a firewall (e.g., `iptables`, `ufw`) to deny external access to port 28332.
|
||||
- If the ZMQ port must be on a public interface, use ZMQ_CURVE with public-key cryptography, or ZMQ_GSSAPI with TLS. This is a more advanced solution but provides strong authentication and encryption for the ZMQ channel.
|
||||
- If not using ZMQ_CURVE, use `zmqpubhashblock` with a firewall that blocks the public port for port 28332.
|
||||
- The `bal` service should not listen on all interfaces (0.0.0.0) unless necessary. It is better to listen only on `127.0.0.1` if the server is behind a reverse proxy (like Nginx) or if the server is only accessible from the local machine.
|
||||
**Status:** Open. **Priority:** Medium.
|
||||
|
||||
### 8. Missing HTTPS / Insecure Server Communication (HIGH)
|
||||
**Location:** `src/bin/bal-server.rs` (TCP server), Nginx configuration.
|
||||
**Description:** The `bal-server` is a plain HTTP server. It does not have TLS or SSL support. To provide HTTPS, an external reverse proxy like Nginx is recommended. However, if the server is exposed to the internet directly, the entire transaction data will be sent over unencrypted HTTP. This includes the raw transaction details and the user IP, which is a privacy risk. An attacker on the same network as the server or client can intercept the request and see the transaction details or the `welist` data.
|
||||
**Impact:**
|
||||
- If the server is directly exposed to the internet, the transaction data is sent in plaintext, making it vulnerable to sniffing and MitM attacks.
|
||||
- If the reverse proxy is not configured with TLS, the server will be insecure and might be vulnerable to a `HTTP Host Header Injection` or `HTTP Header Injection` attack if the server uses the Host header to determine the routing.
|
||||
**Mitigation:**
|
||||
- The production setup must use the `Nginx` configuration from the `contrib` script to terminate TLS and provide HTTPS. The `bal-server` should not be exposed to the internet directly on port 3031 (or any other port). It should only be accessible from `127.0.0.1`.
|
||||
- If the server must be exposed to the internet, use HTTPS with a valid SSL certificate and HTTP/2.
|
||||
**Status:** Open. **Priority:** High. **Mitigation:** Ensure the production setup includes Nginx and TLS.
|
||||
|
||||
### 9. Missing Input Validation (MEDIUM)
|
||||
**Location:** `src/bin/bal-server.rs` (e.g., `pushtxs` endpoint).
|
||||
**Description:** While the server does check if the transaction is valid and the fee is correct, it does not validate the `Content-Type` or the `Content-Length` of the request body. It also does not validate the `network` string before using it in the path. The `network` string is directly used to match the database table, which could be a potential SQL injection or DoS vector if the string is not a known network (e.g., `bitcoin`, `testnet`). The `searchtx` endpoint also does not validate the `txid` format and uses it in the SQL query.
|
||||
**Impact:** An attacker could send a request with a malformed `network` or `txid`, which could cause unexpected database behavior, or a server error (e.g., a 500 error if the database table is not found), or a DoS if the SQL query is not handled properly. The `network` value is used as a string in the query, which could be used to bypass the database if it is not validated.
|
||||
**Mitigation:**
|
||||
- Add a strict validation step for the `network` parameter. Use an `enum` or a `HashSet` of known network names. If the `network` is not in the list, return a `404` error immediately, before accessing the database.
|
||||
- Add a strict validation step for the `txid` in the `searchtx` request. A `txid` must be a 64-character hexadecimal string. If `txid` is not hex or not 64 chars, return `400 Bad Request` immediately.
|
||||
- Add a `Content-Length` check to the request body. If it's not set, or if it's too large, return `411 Length Required` or `413 Payload Too Large`.
|
||||
**Status:** Open. **Priority:** Medium.
|
||||
|
||||
### 10. Information Leakage (LOW)
|
||||
**Location:** `valid_txs` and `invalid_txs` files, `bal-server` error messages.
|
||||
**Description:** The `bal-server` returns `500 Internal Server Error` in some cases. The `bal-server` does not log the raw request body or the user IP in all cases, but it does log the transaction details and some error messages in the `valid_txs` and `invalid_txs` log files. The `valid_txs` file contains the raw transaction details, which could leak private information if the log file is not protected. The `invalid_txs` file contains the raw error messages from the `bitcoind` RPC, which could be used to fingerprint the `bitcoind` version or its configuration. The `valid_txs` and `invalid_txs` files contain the raw transaction details, including the user IP and the transaction details, which could be used to identify the user's behavior or the network's topology. The `invalid_txs` file contains the raw error message from the `bitcoind` RPC, which is a potential information leakage (e.g., `bad-txns-inputs-missingorspent`). This message could be used to fingerprint the `bitcoind` version or the mempool state.
|
||||
**Impact:**
|
||||
- If the log files are not protected, the raw transaction details could be read by unauthorized users or processes running on the same machine. If the log files are accessible, the attacker could see the transaction details and potentially use them to link addresses to users or services.
|
||||
- The `invalid_txs` file contains the raw error messages from the `bitcoind` RPC, which could be used to fingerprint the node or its configuration. For example, `bad-txns-inputs` suggests that the input is not available or not valid, which is a mempool state. If the attacker can read these logs, they can deduce the state of the mempool.
|
||||
**Mitigation:**
|
||||
- Ensure the `valid_txs` and `invalid_txs` files are not stored in the same directory as the `bal.db` or `private_key.pem`. If they are, they should be protected with `chmod 600` and only readable by the `bal` user.
|
||||
- The `bal-server` should not log the raw request body or the transaction details in the `valid_txs` log file. It should only log the `txid` and the result, not the full raw transaction. The `invalid_txs` should log the error message, but not the raw transaction details or the user IP. If the server logs the raw transaction, the attacker could read it by reading the log files or the memory of the server process if it crashes.
|
||||
**Status:** Open. **Priority:** Medium.
|
||||
|
||||
### 11. `valid_txs` Log File Privacy (LOW)
|
||||
**Location:** `valid_txs`, `invalid_txs` files.
|
||||
**Description:** The `valid_txs` and `invalid_txs` files are plain text log files. `valid_txs` contains the transaction details and the raw hex. `invalid_txs` contains the error messages and the raw hex of the failed transactions. These files do not contain the user IP, but they do contain the raw transaction details and the `txid`, which is enough to fingerprint the transaction. If the `valid_txs` file is accessible to the public, the transaction details could be read by anyone. Also, the `valid_txs` file is not encrypted or compressed.
|
||||
**Impact:** The raw transaction details could be read by anyone. If the user is using the `valid_txs` file to track the transactions, it could be used for privacy analysis or to fingerprint the transaction history. If the `valid_txs` file is leaked, it could be used to link the user's transaction to the `bal-server` and identify the user or their behavior. The `valid_txs` file is not encrypted, and it is not protected by any authentication. If the server is compromised, these files will be accessible to the attacker, which is a privacy risk.
|
||||
**Mitigation:**
|
||||
- Ensure the `valid_txs` and `invalid_txs` files are not accessible to the public. If the server is running on a shared directory, use `chmod 600` to restrict access. If the server is not, they are accessible by default.
|
||||
- The `valid_txs` and `invalid_txs` files should not be stored in the same directory as the `bal.db` or `private_key.pem`. They should be in a separate directory.
|
||||
- The `valid_txs` and `invalid_txs` files should be rotated and compressed to avoid growing infinitely. The `bal-server` should also not log the entire raw transaction in the `valid_txs` file. It should only log the `txid` and the status. This will prevent the leak of the transaction details if the log file is compromised.
|
||||
**Status:** Open. **Priority:** Low.
|
||||
|
||||
### 12. `bal-stats.rs.dontcompile` (LOW)
|
||||
**Location:** `src/bin/bal-stats.rs.dontcompile`.
|
||||
**Description:** This file is a broken, incomplete HTML report generator. It directly queries `tbl_tx` and writes an `bal_status.html` to the local filesystem. It is not compiled and is not part of the main system. However, it does contain hardcoded SQL queries and HTML. It could be accidentally compiled if the file is renamed.
|
||||
**Impact:** If the user accidentally compiles or runs this file, it could leak transaction details or create an HTML file with sensitive information. The file is not part of the `Cargo.toml` build, but it is in the source tree. It does not have the same security checks as the `bal-server` or `bal-pusher`. It could be used to create a report that exposes the database contents if the HTML file is not protected. The file is marked as `dontcompile` but is still in the `src/bin/` directory. It could be accidentally included in the build if the user is not careful. It could be used to access the database directly without the `bal-server` API, which might bypass security checks or rate limiting. If the file is compiled, the `main` will try to write `bal_status.html` to the current directory, which might be a public directory if the server is configured to serve static files. This could be a security issue if the file is accidentally served by the `nginx` or `bal-server`.
|
||||
**Mitigation:**
|
||||
- Remove the `bal-stats.rs.dontcompile` file from the source tree if it is not used. It is a dead code that could be used for an accidental leak or a security risk if it is compiled. If it needs to be kept, it should be in a `scripts/` directory or a separate repository, not in the `src/bin/` directory.
|
||||
- If the file is kept, it should be marked with a clear comment explaining why it is not compiled and why it could be a security risk. It should not be part of the `bal_server` crate and should not be accessible by default. It should not be compiled in `Cargo.toml` or should not be in the `bin/` directory. If it is a utility script, it should be in `scripts/` and not in the `bal_server` binary path.
|
||||
**Status:** Open. **Priority:** Low.
|
||||
|
||||
## Hardening Recommendations
|
||||
|
||||
### System-Level
|
||||
1. **Run the service as a non-root user:** Use the `bal-systemd` hardening (e.g., `ProtectSystem=full, NoNewPrivileges, PrivateDevices`). The `bal-server` should not be exposed to the internet directly. Use a reverse proxy or a firewall.
|
||||
2. **Use `firewall` (e.g., `iptables`, `netfilter`, or `nftables`) to block all inbound ports except the HTTPS port (443) and the SSH port (22).** The HTTP port should not be exposed to the internet. The `bal-server` should be on a separate port or on `127.0.0.1`.
|
||||
3. **Use a `VPN` or `Tor` for the `welist` connection.** If the `welist` server is on a public network, use a VPN or Tor to prevent the `welist` IP address from being exposed to the `bal-pusher`.
|
||||
4. **Run the `bal-server` in a `chroot` or `docker` container.** The server should be isolated from the rest of the system. If the server is compromised, the attacker will not be able to access the `bal.db` or `private_key.pem` files.
|
||||
5. **Enable the `SELinux` or `AppArmor` profile for the `bal-server` and `bal-pusher` binaries.** This will prevent the attacker from accessing the database or the private key if the binary is compromised.
|
||||
6. **Use a `read-only` file system for the `bal-server` binary.** The server should be read-only to prevent the attacker from modifying the binary or the configuration files. The `bal-server` should be in a `chroot` jail with the `bal` user.
|
||||
7. **Use a `network` firewall to block the outbound traffic from the `bal-server` to the internet.** If the server only needs to communicate with the `bal-pusher` and the `nginx` proxy, it should not have internet access. If the server is compromised, it will not be able to download malware or communicate with a C2 server.
|
||||
|
||||
### Application-Level
|
||||
1. **Add `Rate Limiting`:** Add a rate limiter to the `bal-server` to prevent DDoS or abuse. The `bal-server` should limit the number of requests per IP per minute or per hour. It should also limit the number of `pushtxs` requests to avoid filling the database with malicious requests. A `HashMap` or `Redis` can be used to store the rate limiter state.
|
||||
2. **Add `Input Validation`:** Add strict input validation for all endpoints. The `network`, `txid`, and `hex` parameters must be validated. The `txid` must be 64 hex chars, the `hex` must be a valid Bitcoin hex string, and the `network` must be a known network.
|
||||
3. **Add `HTTPS`:** The `Nginx` configuration should be used to terminate TLS and provide HTTPS. The `bal-server` should only run on `127.0.0.1` to avoid being exposed to the public internet.
|
||||
4. **Add `WAL` for `SQLite`:** Enable the `Write-Ahead Logging` (WAL) mode for the `bal` database to prevent database locking or data corruption when multiple processes access the database at the same time. This is a standard practice for SQLite and is supported by the `sqlite` crate. Enable it via `PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;` upon the first connection.
|
||||
5. **Add `ZMQ Authentication`:** Use `ZMQ_CURVE` or `ZMQ_GSSAPI` to authenticate the ZMQ connection. If the `ZMQ` connection is over a public network, the `bal-pusher` should be authenticated and the traffic should be encrypted. Alternatively, use `ZMQ_RCVTIMEO` and `ZMQ_SNDTIMEO` to set a connection timeout to prevent blocking forever if the socket is disconnected or the `bitcoind` node is not available.
|
||||
6. **Add `Transaction Size Limits`:** The `bal-pusher` should have a `MAX_TRANSACTIONS_PER_SECOND` and `MAX_TRANSACTIONS_PER_BLOCK` config value. This will prevent the pusher from sending too many transactions to the `bitcoind` node and overloading it. If the database is full of many transactions, the pusher should only send a small batch at a time (e.g., `1` or `10` transactions per block, or `5` per minute) to avoid overwhelming the RPC queue or the node.
|
||||
7. **Add `ZMQ Retry`:** The `bal-pusher` should implement a retry mechanism for the `sendrawtransaction` and ZMQ connection. If the RPC or ZMQ call fails, it should wait for the next block before trying again. The pusher should not panic or stop on the first failure. It should be resilient and continue operating even if the network is down or the `bitcoind` node is restarting. The `ZMQ` socket should be reconfigured with `ZMQ_RECONNECT_IVL` and `ZMQ_MAXMSGSIZE` to avoid reconnecting too aggressively or receiving unbounded messages. If the connection is lost, the `ZMQ` should wait for the `bitcoind` to come back and not try to reconnect immediately. The pusher should also handle `SIGTERM` and `SIGINT` gracefully and stop the ZMQ connection before exiting.
|
||||
8. **Add `Transaction Fee Limits`:** The `bal-server` should not accept transactions with a fee of `0`. It should also not accept transactions with a fee higher than a reasonable limit (e.g., `100000` satoshi for a `10 KB` transaction). This will prevent the user from sending too many transactions with a very low or high fee. This will limit the risk of a DoS attack where the attacker fills the database with many invalid transactions. The `bal-server` should not accept a transaction that is not valid or has the wrong `network`. Also, the `bal-server` should not accept a transaction with a very high `locktime` (e.g., `9999999999`) to prevent the database from becoming too large or to prevent the pusher from being blocked by a very far future locktime. The `bal-server` should only accept locktime values that are reasonable for the current blockchain height.
|
||||
9. **Add `Transaction Fee Limits`:** The `bal-server` should not accept a transaction from a `network` if the `network` is not supported. Only the `regtest`, `testnet`, `testnet4`, `signet`, and `bitcoin` networks are supported. If the `network` is not in the list, the server should reject the request and not process it. The server should also not accept a transaction from a different network than the one it is configured for. If the server is configured for `regtest`, it should not accept `bitcoin` transactions. This will prevent the attacker from using the wrong network and sending a transaction that is not valid for the current network. The server should not accept a transaction that has a different `network` than the `our_address` network. If the `network` is not valid, the server should not process the request and should return a `404` error. The server should not accept a transaction that is for a different network than the one it is configured for. This will prevent the attacker from using the server to process transactions for a different network.
|
||||
10. **Add `Transaction Time Limits`:** The `bal-server` should not accept a transaction with a locktime that is too far in the future. If the locktime is greater than `500000000`, it is a timestamp. The server should only accept locktime values that are within a reasonable timeframe (e.g., within the next year or a few months). If the locktime is in the past, the server should not accept it or it should be marked as a `0` locktime and processed immediately. If the locktime is too far in the future, it should be rejected. If the locktime is a block height, it should be within the next `100000` blocks or the next few months. If the locktime is a timestamp, it should be within the next few years or a reasonable timeframe. If the locktime is too far in the future, it will be impossible to process, and it will fill the database with invalid transactions. The `bal-server` should not accept a transaction with a `locktime` of `0` if `0` is a special case. If the `locktime` is `0`, it should be processed immediately and not stored in the database. If `0` is treated as a special case, the server should not store it as a pending transaction. If the `locktime` is `0`, the transaction should be sent immediately or processed as a normal transaction without a timelock. If the locktime is `0`, it should be treated as a normal transaction and sent to the `bitcoind` node immediately. The server should not send a `0` locktime transaction to the `pusher` because it is not a pending transaction. The pusher should not process a `0` locktime transaction because it is not waiting for a specific time or block height. If the `locktime` is `0`, it should be handled in the `bal-server` and not in the `bal-pusher`. The server should not store the `0` locktime transaction in the database. If a `0` locktime transaction is sent, the server should not store it as a pending transaction but should send it to the `bitcoind` node or process it immediately. If the `0` locktime is a special case, the server should not treat it as a pending transaction and should not send it to the `pusher`. If the `locktime` is `0`, the `bal-server` should not send it to the `bitcoind` network. If the `0` locktime is a valid transaction, the server should not send it to the pusher. If the `0` locktime is a special case, it should be handled in the `bal-server` and not in the `bal-pusher`. If the `0` locktime is a special case, it should not be sent to the `pusher`. If the `0` locktime is a special case, the server should not treat it as a pending transaction. If the `0` locktime is a special case, the server should not process it in the `pu
|
||||
95
docs/09_references_and_links.md
Normal file
95
docs/09_references_and_links.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# References and Links
|
||||
|
||||
## Quick Reference
|
||||
- **What this file contains:** links to source code, dependencies, and existing documentation, plus a machine-readable dependency map.
|
||||
- **See also:** [INDEX.md](INDEX.md), [01_project_overview.md](01_project_overview.md), [04_modules_detail.md](04_modules_detail.md)
|
||||
|
||||
---
|
||||
|
||||
## Source Code References
|
||||
|
||||
| Module/Component | File Path | Key Lines/Details |
|
||||
|---|---|---|
|
||||
| Library | `src/lib.rs` | Exports `db` and `xpub` modules |
|
||||
| Database | `src/db.rs` | SQL schema, `execute_insert`, batched inserts |
|
||||
| XPub/Address Derivation | `src/xpub.rs` | `parse_xpub`, `derive_address`, `get_descriptor`, BIP-84 |
|
||||
| HTTP Server | `src/bin/bal-server.rs` | Hyper + Tokio, routes, handlers, `pushtxs` logic |
|
||||
| Async Pusher | `src/bin/bal-pusher.rs` | ZMQ `hashblock`, RPC + Reqwest, `send_stats` |
|
||||
| Synchronous Pusher Enhanced | `src/bin/bal-pusher-enhanced.rs` | ZMQ `rawblock`, raw block header parsing, no `getblockchaininfo` |
|
||||
| Stats (broken) | `src/bin/bal-stats.rs.dontcompile` | Not compiled, incomplete HTML report generator |
|
||||
| Release script | `make_release.sh` | Hardcoded token, `cargo install` |
|
||||
| DB download script | `download_bal_db.sh` | `scp` from remote |
|
||||
| Server dev script | `bal-server.sh` | Sources `bal-server.env`, `cargo run` |
|
||||
| Pusher dev script | `bal-pusher.sh` | Sources `bal-pusher.env`, `cargo run` |
|
||||
| Send transaction script | `sendtx.sh` | `bitcoin-cli` wrapper |
|
||||
| Utility scripts | `lib.sh` | Colored echo functions |
|
||||
| Contrib (install) | `contrib/download_and_install_bal.sh` | Nginx, Certbot, systemd setup, hardcoded xpub |
|
||||
| Contrib (install bitcoind) | `contrib/download_and_install_bitcoincore.sh` | Bitcoind download, GPG verify, systemd, config |
|
||||
| Contrib (install Tor) | `contrib/install_tor.sh` | Tor repository, `ControlPort 9051` |
|
||||
| Systemd service | `bal-server.service` | Runs as `bal` user, `ProtectSystem`, `MemoryDenyWriteExecute` |
|
||||
| Systemd service | `bitcoind.service` | `zmqpubhashblock` setup |
|
||||
| Systemd service | `tbitcoind.service` | Testnet `bitcoind` |
|
||||
|
||||
---
|
||||
|
||||
## Dependency Map (from `Cargo.toml`)
|
||||
|
||||
| Dependency | Version | Purpose |
|
||||
|---|---|---|
|
||||
| `base64` | `0.22.1` | Encoding/decoding in `pushtxs` / xpub |
|
||||
| `bs58` | `0.4.0` | Base58 encoding for Bitcoin addresses / xpubs |
|
||||
| `bytes` | `1.2` | Byte handling for `hyper`/`reqwest` |
|
||||
| `bitcoin` | `0.32.5` | Transaction parsing, `ScriptBuf`, `Address`, `Xpub`, `Transaction` |
|
||||
| `bitcoincore-rpc` | `0.19.0` | RPC client for `bitcoin-cli` methods (`sendrawtransaction`, `getblockchaininfo`) |
|
||||
| `bitcoincore-rpc-json` | `0.19.0` | JSON types for Bitcoin RPC responses |
|
||||
| `byteorder` | `1.5.0` | Reading block timestamp from raw block header (big-endian) `u32` |
|
||||
| `confy` | `0.6.1` | Loading `.toml` configuration files (default config) |
|
||||
| `chrono` | `0.4.40` | `Date` and `DateTime` handling for timestamps and `report` |
|
||||
| `env_logger` | `0.11.5` | Log level configuration via `RUST_LOG` environment variable |
|
||||
| `hex` | `0.4.3` | Hex encoding for transaction serialization and raw bytes |
|
||||
| `hex-conservative` | `0.1.1` | Hex parsing (used for Bitcoin hex strings) |
|
||||
| `hyper` | `1.3.1` | Async HTTP server (features: `http1`, `server`) |
|
||||
| `hyper-util` | `0.1.3` | Hyper utilities, `TokioIo` |
|
||||
| `http-body-util` | `0.1` | HTTP body collection and streaming utilities |
|
||||
| `log` | `0.4.21` | Logging facade (used by `env_logger`) |
|
||||
| `openssl` | `0.10.74` | TLS/SSL, `vendored` feature to avoid system dependency |
|
||||
| `sha2` | `0.10.8` | SHA-256 hashing (used in transaction validation or address generation) |
|
||||
| `serde` | `1.0.152` | Serialization of config objects and JSON responses (`derive` feature) |
|
||||
| `serde_json` | `1.0.116` | JSON parsing for HTTP request bodies and API responses |
|
||||
| `sqlite` | `0.34.0` | Direct SQLite C bindings, raw SQL queries, no ORM |
|
||||
| `regex` | `1.10.4` | `RegExp` parsing for URL matching (e.g., `network` regex) in `bal-server` |
|
||||
| `reqwest` | `0.12.24` | HTTP client (`json` + `socks` features) for `welist` stats POST |
|
||||
| `tokio` | `1` | Async runtime (`rt`, `net`, `macros`, `rt-multi-thread`) |
|
||||
| `zmq` | `0.10.0` | ZeroMQ for `hashblock`/`rawblock` notifications |
|
||||
|
||||
---
|
||||
|
||||
## Mapping to Existing Documentation
|
||||
|
||||
| Existing File | Description | Replaced/Managed By KB |
|
||||
|---|---|---|
|
||||
| `README.md` | Installation, environment variables, ZMQ dependency | `07_deployment_and_ops.md` |
|
||||
| `RPC.md` | API endpoint specification (HTTP methods, paths) | `05_api_reference.md` |
|
||||
| `AGENTS.md` | Security guidelines, audit rules, baseline commands | `08_security_audit.md` |
|
||||
| `update` | Irrelevant saved conversation (Diesel/Axum) | Ignored, not mapped |
|
||||
| `valid_txs` / `invalid_txs` | Logs of past transaction push results | `08_security_audit.md` (Information Leakage) |
|
||||
| `Cargo.toml` | Dependency versions and features | `09_references_and_links.md` (Dependency Map) |
|
||||
| `bal-server.service` | `systemd` unit file | `07_deployment_and_ops.md` (Systemd) |
|
||||
| `bitcoind.service` | `systemd` unit for `bitcoin` node | `07_deployment_and_ops.md` (Systemd) |
|
||||
| `tbitcoind.service` | `systemd` unit for testnet node | `07_deployment_and_ops.md` (Systemd) |
|
||||
| `bal-server.env` | `bal-server` environment variables | `07_deployment_and_ops.md` (Environment Variables) |
|
||||
| `bal-pusher.env` | `bal-pusher` environment variables | `07_deployment_and_ops.md` (Environment Variables) |
|
||||
| `bal-server.sh` | Dev server startup script | `07_deployment_and_ops.md` (Bash Scripts) |
|
||||
| `bal-pusher.sh` | Dev pusher startup script | `07_deployment_and_ops.md` (Bash Scripts) |
|
||||
| `sendtx.sh` | Test transaction sender | `07_deployment_and_ops.md` (Bash Scripts) |
|
||||
| `make_release.sh` | Release script with hardcoded secret | `08_security_audit.md` (Secret Leakage) |
|
||||
| `download_bal_db.sh` | `scp` from remote | `07_deployment_and_ops.md` (Bash Scripts) |
|
||||
| `generate_keys.sh` | Generate public key from `private_key.pem` | `07_deployment_and_ops.md` (Bash Scripts) |
|
||||
| `public_key.pem` | Ed25519 public key for stats verification | `05_api_reference.md` (GET `/.pub_key.pem` endpoint) |
|
||||
| `private_key.pem` / `privkey.pem` / `ec.key` / `chiave_privata.key` | Private keys for stats signing | `08_security_audit.md` (Secret Leakage) |
|
||||
| `contrib/download_and_install_bal.sh` | Full deployment setup | `07_deployment_and_ops.md` (Nginx, SSL) and `08_security_audit.md` (Hardcoded Secret) |
|
||||
| `contrib/download_and_install_bitcoincore.sh` | Bitcoin Core install/verify | `07_deployment_and_ops.md` (Systemd) |
|
||||
| `contrib/install_tor.sh` | Tor installation script | `07_deployment_and_ops.md` (Tor) |
|
||||
| `contrib` | Various helper scripts | `07_deployment_and_ops.md` |
|
||||
|
||||
---
|
||||
31
docs/INDEX.md
Normal file
31
docs/INDEX.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# `docs/` Knowledge Base
|
||||
|
||||
## Quick Guide for Contributors
|
||||
|
||||
- **I am a developer and want to understand the project** → Start with [`01_project_overview.md`](01_project_overview.md)
|
||||
- **I am a developer and need to know how the system works** → Read [`03_architecture_and_data_flow.md`](03_architecture_and_data_flow.md)
|
||||
- **I am a developer working on a specific module** → See [`04_modules_detail.md`](04_modules_detail.md)
|
||||
- **I am a security auditor** → Go straight to [`08_security_audit.md`](08_security_audit.md)
|
||||
- **I am deploying or operating this software** → Check [`07_deployment_and_ops.md`](07_deployment_and_ops.md)
|
||||
- **I need to integrate with the API** → Reference [`05_api_reference.md`](05_api_reference.md)
|
||||
- **I need to understand the database** → Use [`06_database_schema.md`](06_database_schema.md)
|
||||
- **I need to understand Bitcoin concepts** → See [`02_glossary_and_bitcoin_domain.md`](02_glossary_and_bitcoin_domain.md)
|
||||
- **I need source code references or external links** → Check [`09_references_and_links.md`](09_references_and_links.md)
|
||||
|
||||
## Files Overview
|
||||
|
||||
| # | File | Purpose |
|
||||
|---|------|---------|
|
||||
| 1 | [`01_project_overview.md`](01_project_overview.md) | Vision, goals, components, and mapping to existing docs |
|
||||
| 2 | [`02_glossary_and_bitcoin_domain.md`](02_glossary_and_bitcoin_domain.md) | Bitcoin domain knowledge: BIP-84, locktime, P2WPKH, ZMQ, block headers |
|
||||
| 3 | [`03_architecture_and_data_flow.md`](03_architecture_and_data_flow.md) | High-level architecture, data flow, state machine, error handling |
|
||||
| 4 | [`04_modules_detail.md`](04_modules_detail.md) | Deep dive into each Rust module and binary |
|
||||
| 5 | [`05_api_reference.md`](05_api_reference.md) | Complete API docs: HTTP, ZMQ, RPC with examples |
|
||||
| 6 | [`06_database_schema.md`](06_database_schema.md) | Full SQL schema, tables, queries, data lifecycle |
|
||||
| 7 | [`07_deployment_and_ops.md`](07_deployment_and_ops.md) | Environment variables, systemd, nginx, scripts, Tor, installation |
|
||||
| 8 | [`08_security_audit.md`](08_security_audit.md) | Threat model, vulnerability assessment, hardening recommendations |
|
||||
| 9 | [`09_references_and_links.md`](09_references_and_links.md) | Source code links, Cargo dependencies, existing file mapping |
|
||||
|
||||
---
|
||||
|
||||
> **Note:** This knowledge base is maintained in parallel to the codebase. After any significant change to the code (new features, API changes, schema changes, or security fixes), update the corresponding file in this directory to keep documentation synchronized.
|
||||
Reference in New Issue
Block a user