- Add make_ping_key_revocation.py: onion revocations are signed by the identity key, DNS TXT revocations are self-signed by the revoked key - Document the revocation format, endpoint, helper usage, lifetime and no-revocation-of-identity-key limits - Update the security audit, references and env docs for the new flow and the v2/onion-only scope
9.3 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.
For .onion URLs the pusher includes a delegation object when
WELIST_PING_DELEGATION_FILE is set and WELIST_PING_SIG_VERSION=2. The onion
identity key signs that certificate offline; SSL_KEY_PATH is then the
dedicated ping key, and the service verifies the certificate before checking
the ping signature. The service normalizes onion (lowercase, trailing dot
removed) before comparing it with the ping host; the pusher's startup check uses
the exact configured host, so issue lowercase certificates.
The pusher validates the certificate at startup: onion must match the
configured host, valid_until must be in the future, and key must match
SSL_KEY_PATH. If the file is missing or malformed, or any check fails, the
pusher logs a warning and sends no delegation object. It then signs with
SSL_KEY_PATH directly; in the recommended setup that key is the dedicated
ping key, so the service rejects the pings until delegation is restored.
The url is trimmed of surrounding whitespace and trailing slashes, and the
chain is lowercase, matching the service-side canonicalization.
To stop accepting a ping key, run contrib/make_ping_key_revocation.py and
submit the result to {WELIST_SERVER_URL}/ping-keys/revoke. Onion revocations
are signed by the onion identity key; DNS TXT revocations are self-signed by the
key being revoked. See 10_dnssec_key_publishing.md
for the operator guide on publishing _welist-key.<host>, onion delegation and
revocation.
Version 1 (legacy)
- Concatenates
url + chain + height + median_time + last_block_hash. - Signs that string with the Ed25519 private key.
- POSTs those five fields plus the base64 signature to
{WELIST_SERVER_URL}/ping. Noinfo,base_feeorversionis sent. - The
welistserver verifies the signature with the public key served atGET /.pub_key.pem. - The v1 signature authenticates only liveness. The service refreshes
last_updatefor an accepted ping but does not persist executor metadata. Because any metadata added to a v1 ping would be outside the signature, use v2 wheninfo,base_feeandversionmust be published and authenticated.