- Add WELIST_PING_SIG_VERSION (1 legacy, 2 canonical JSON); default 1
- Sign WELIST-PING-V2\n + compact sorted JSON covering url, chain, height,
median_time, last_block_hash, info, base_fee, version and sig_version
- Fetch info/base_fee/version from the local /{chain}/info endpoint and sign them
- Fall back to null metadata on fetch failure; skip the fetch for onion URLs
- Normalize the advertised URL and bound metadata to match the WeTime service
- Activate v2 in bal-pusher.sh and document the TXT record setup
- Add unit tests for canonical payloads, URL normalization, metadata and onion detection
Includes previously uncommitted local deployment hardening:
- pg-server.sh configurable loopback bind address, volume and recreate handling
- .env loading in bal-server.sh and bal-pusher.sh
- second bal-server/bal-pusher development scripts
7.7 KiB
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, 04_modules_detail.md, 06_database_schema.md, 07_deployment_and_ops.md
HTTP API (provided by bal-server)
Rate Limiting
All endpoints are rate-limited via actix-governor with a token-bucket algorithm. The rate limit key is the real client IP address, extracted from X-Real-IP / X-Forwarded-For headers when the request comes from a trusted proxy (default 127.0.0.1, configurable via BAL_SERVER_TRUSTED_PROXY). Direct connections (non-proxy) use the TCP peer IP.
Default: 1 req/s with burst of 3 (configurable via BAL_SERVER_ACTIX_PUSHTXS_PER_SEC / BAL_SERVER_ACTIX_PUSHTXS_BURST).
When behind Nginx, ensure proxy_set_header X-Real-IP $remote_addr is set so the server can identify individual clients.
GET /
- Description: Returns a static identification string (default: "Will Executor Server").
- Response: Plain text
200 OK.
GET /version
- Description: Returns the Cargo package version.
- Response:
text/plain(e.g.,0.3.2).
GET /.pub_key.pem
- Description: Returns the static Ed25519 public key PEM file for signature verification of remote stats.
- Response:
text/plainwith the PEM file content. - File:
public_key.pemin the project root (path configurable viaBAL_SERVER_PUB_KEY_PATH).
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):
{ "address": "bcrt1q...", "base_fee": 50000, "chain": "regtest", "info": "Will Executor Server", "version": "0.3.2" } - In xpub mode, the
addressfield contains a freshly derived P2WPKH address unique to the requesting IP. - Error:
404if the network is not configured or unknown.
GET /:network/stats
- Description: Returns statistics for the given network. Guarded by the
expose_statsconfiguration flag. - Response (200 OK): A JSON array of
StatsResponseobjects:[ { "report_date": "2024-07-20T12:00:00Z", "chain": "regtest", "totals": 42, "waiting": 10, "sent": 30, "failed": 2, "waiting_profit": 10000, "sent_profit": 30000, "missed_profit": 5000, "unique_inputs": 15 } ] - Error:
403or400if stats are not enabled or the network is unknown.
POST /:network/pushtxs
- Description: Accepts one or more raw hex Bitcoin transactions (newline-separated). The server deserializes each transaction, validates that the fee is paid to the correct
our_addressfor that network, and stores valid transactions in the database. - Request Body: Newline-separated raw hex transactions.
02000000000101...hex...\n 02000000000101...hex...\n - Response (200 OK): A JSON object with the results for the batch:
{ "accepted": 2, "rejected": 0, "details": [...] } - Response (400 Bad Request): If all transactions are invalid, the fee is missing, or the locktime is not acceptable.
- Response (413 Payload Too Limited): If the request body exceeds the configured max size (default 1 MiB).
- Security Note: Invalid transactions or those not paying the required fees are not inserted into the database.
POST /searchtx
- Description: Searches for a transaction by its
txid. The request body must contain exactly 64 hex characters. - Request Body:
{ "txid": "abc123def456..." } - Response (200 OK):
{ "txid": "abc123...", "status": 1, "tx": "020000000...", "our_address": "bcrt1q...", "our_fees": 1000, "reqid": "192.168.1.1" } - Response (400 Bad Request): If the txid is not exactly 64 hex characters.
- Response (404 Not Found): If the transaction is not found in the database.
ZMQ Messages (consumed by bal-pusher)
Topic: hashblock
- 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
getblockchaininfofrom the RPC, gets the updatedmediantimeandblocksheight, then queries and pushes pending transactions. - Endpoint: Per-network (e.g.,
tcp://127.0.0.1:28332for mainnet). - Timeout: 5 seconds (
ZMQ_RCVTIMEO). The pusher logs a warning after ~720 consecutive timeouts (~1 hour).
Bitcoin Core RPC Usage (used by bal-pusher)
sendrawtransaction
- Method:
sendrawtransaction(RPC2) - 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) with the error inpush_err.
getblockchaininfo
- Method:
getblockchaininfo(RPC1) - Parameters: None.
- Description: Returns the current blockchain state, including
mediantime(median timestamp of the last 11 blocks) andblocks(best block height). Used to evaluatenLockTimeof pending transactions.
getblock
- Method:
getblock(RPC1) - Parameters:
blockhash,verbosity(set to1for JSON with timestamp). - Description: Fetches block details. Used as an alternative for median time calculation.
RPC Authentication
Authentication is done via bitcoincore-rpc using either:
UserPass:BAL_PUSHER_{NETWORK}_RPC_USERandBAL_PUSHER_{NETWORK}_RPC_PASSWORD.CookieFile:$HOME/.bitcoin/{dir_path}.cookieor custom path viaBAL_PUSHER_{NETWORK}_COOKIE_FILE.
The client tries username/password auth first, then falls back to cookie file auth.
Ed25519 WeTime Ping Signing
The pusher signs the report before sending it to the welist server. The
scheme is selected with WELIST_PING_SIG_VERSION:
Version 2 (canonical JSON, preferred)
- Fetches
info,base_feeandversionfrom the localGET /{chain}/infoendpoint (failure falls back tonull). - Builds a compact JSON object with lexicographically sorted keys, every field
present, and
nullfor absent optional values:base_fee,chain,height,info,last_block_hash,median_time,sig_version,url,version. - Signs
WELIST-PING-V2\nfollowed by that JSON with the Ed25519 private key (SSL_KEY_PATH). - POSTs the same fields plus the base64 signature and
"sig_version": 2to{WELIST_SERVER_URL}/ping. - The
welistserver discovers the public key from a DNSSEC-validated_welist-key.<host>TXT record (v=we1; alg=ed25519; key=<base64 SPKI DER>) or from a v3.onionaddress. No HTTP callback to/.pub_key.pemis made.
The url is trimmed of surrounding whitespace and trailing slashes, and the
chain is lowercase, matching the service-side canonicalization.
Version 1 (legacy)
- Concatenates
url + chain + height + median_time + last_block_hash. - Signs that string with the Ed25519 private key.
- POSTs the fields plus the base64 signature to
{WELIST_SERVER_URL}/ping. - The
welistserver verifies the signature with the public key served atGET /.pub_key.pem; this scheme only refresheslast_update.