Chainquery reads the LBRY blockchain into a structured MySQL database so that blocks, transactions, addresses, and the full LBRY claim system (channels, publishes, supports, tips/purchases, tags) can be queried quickly with plain SQL instead of walking the chain over RPC.
It connects to a LBRY full node over JSON-RPC — lbcd in production (LBRY's Go full-node implementation, which the stack has migrated to), or the original lbrycrd — ingests blocks as they are mined, keeps the database in sync (including across blockchain reorganizations), computes derived data via scheduled jobs, and exposes an HTTP API — including a public, read-only SQL endpoint.
Everything runs from a single binary (chainquery serve), which starts the API
server and the daemon together. There are four logical parts.
The daemon (daemon/daemon.go, daemon/processing/) keeps the database in sync
with lbrycrd. On each iteration it asks lbrycrd for the current height and
processes every block above the last height it stored.
blockQueue → blockProcessedChan) plus a global BlockLock mutex guarantee
block N+1 never starts before N is committed; out-of-order processing panics by
design to avoid corrupting the data.maxparalleltxprocessing, default NumCPU) drains a job queue. Because a
transaction can spend an output created by another transaction in the same
block, failed transactions are pushed onto a redo queue and retried up to
maxfailures times (default 1000). Transactions are pre-sorted by dependency
depth (optimizeOrderToProcess) so parents tend to run before children.daemonmode): beast (0, no delay),
slow-and-steady (1, 100ms/block), delay (2, configurable), and daemon (3,
one block per daemon iteration).Entry points: daemonIteration(), processing.RunBlockProcessing(stopper, height),
processing.ProcessTx(...).
The API server (swagger/apiserver/, handlers in apiactions/) binds to
apihostport (default 0.0.0.0:6300) and uses a separate read DSN
(apimysqldsn). Routes (swagger/apiserver/go/routers.go):
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /api/ |
Index — returns Hello World! |
none |
| GET | /api/sql |
Public read-only SQL. Runs the query param against MySQL with an injected MAX_EXECUTION_TIME and a maxsqlapitimeout cap |
none |
| GET | /api/addresssummary |
Address received / spent / balance | none |
| GET | /api/status |
Table names and sizes | none |
| GET | /api/validate |
Validate chain data | none |
| GET | /api/process |
Process a block or range of blocks | API key |
| GET | /api/sync/name |
Re-sync claimtrie state for a claim name | API key |
| GET | /api/sync/addresses |
Sync address balances | API key |
| GET | /api/sync/txvalues |
Sync transaction values | API key |
| GET | /metrics |
Prometheus metrics | basic auth |
API-key endpoints are rejected unless the supplied Key is listed in the
apikeys config (empty by default = disabled). Prometheus /metrics is guarded
by promuser/prompass when set.
API docs (Swagger, WIP): https://lbryio.github.io/chainquery/ — spec lives at
swagger/apiserver/api/swagger.yaml.
Periodic jobs (daemon/jobs/) compute data that isn't directly part of the raw
blockchain, or that is faster to precompute. Scheduled in initJobs():
| Job | Interval | Purpose |
|---|---|---|
| Claimtrie Sync | 15m | Claim status/effective amount from lbrycrd's ClaimTrie |
| Mempool Sync | 1s | Unconfirmed transactions |
| Certificate Sync | 5s | Channel (certificate) data |
| Chain Sync | 5s | Chain-derived data (must run < 2.5m, see code note) |
| Validate Chain | 24h | Integrity validation |
| Address Balance Sync | 24h | Recompute address balances |
| Transaction Value Sync | 24h | Recompute transaction values |
| Claim Count in Channel | 24h | Number of claims per channel |
Jobs can also be run one-off via chainquery run <job> (see CLI below). The
job_status table records each job's last run.
daemon/upgrademanager/ runs data/schema upgrade scripts between versions on
startup. The application_status table tracks the app/data/api versions so the
manager knows which scripts to apply.
The schema is the fundamental blockchain types — block, transaction,
input, output, address — enriched with the LBRY claim system: claim,
support, purchase, tag, claim_tag, claim_in_list, abnormal_claim,
plus bookkeeping tables (job_status, application_status). Models in
model/ are generated by SQLBoiler.
db/chainquery_schema.sqlChainquery can emit real-time events:
socketyurl / socketytoken) — a new_block notification is
sent on every processed block.config) — webhook URLs for payment and new_claim events.slackbottoken, slackchannel, slackloglevel) — Slack app log
forwarding via chat.postMessage, including reorg depth warnings.go.mod).chainquery database and a user that can access it. The
default DSN expects user chainquery / password chainquery:sql
CREATE DATABASE IF NOT EXISTS chainquery;
CREATE USER 'chainquery'@'localhost' IDENTIFIED BY 'chainquery';
GRANT ALL ON chainquery.* TO 'chainquery'@'localhost';
MySQL must allow stored functions: set log_bin_trust_function_creators = 1.
txindex=1 for lbcd, -txindex for lbrycrdd) —
chainquery relies on getrawtransaction.lbrycrdurl (rpc://lbry:lbry@localhost:9245). Set the node's
rpcuser/rpcpass (in ~/.lbcd/lbcd.conf or ~/.lbrycrd/lbrycrd.conf) to
match, or override lbrycrdurl.notls=1 for a local plaintext RPC.lbrycrdurl) and the Go package (lbrycrd/) keep the
historical name regardless of which node you actually run.Chainquery is configured via a TOML file. The annotated default is
config/default/chainqueryconfig.toml.
Resolution order: $HOME, the working directory, then the in-repo default
(override with --configpath).
Common settings:
| Key | Default | Purpose |
|---|---|---|
lbrycrdurl |
rpc://lbry:lbry@localhost:9245 |
LBRY full-node RPC (lbcd in prod, or lbrycrd) |
mysqldsn |
chainquery:chainquery@tcp(localhost:3306)/chainquery |
Daemon (read/write) DB |
apimysqldsn |
same as mysqldsn |
API server (read) DB |
apihostport |
0.0.0.0:6300 |
API bind address |
blockchainname |
lbrycrd_main |
Chain params (_main / _testnet / _regtest) |
daemonmode |
0 |
Processing throttle mode |
maxfailures |
1000 |
Per-transaction retries before block rollback |
maxparalleltxprocessing |
NumCPU |
Tx worker count per block |
maxsqlapitimeout |
5 |
Max seconds for /api/sql queries |
apikeys |
[] |
Keys allowed to call authorized endpoints |
The repository ships a prebuilt Linux binary in bin/, but to build
from source:
git clone https://github.com/lbryio/chainquery.git
cd chainquery
go build -o chainquery .
scripts/build.sh produces a static (CGO_ENABLED=0) Linux release binary at
bin/chainquery with version metadata baked in.
Initialize the database schema (runs the migrations), then start the service:
./chainquery serve db # create/upgrade the schema
./chainquery serve # run the daemon + API server
-help lists all flags. -d/--debug and -t/--trace raise log verbosity.
| Command | Description |
|---|---|
chainquery serve |
Run the daemon and API server (the main mode) |
chainquery serve db |
Create/upgrade the database schema and exit |
chainquery run <job> |
Run a single job: claimcount, claimtrie, certificate, mempool, transactionvalue, chain, outputfix |
chainquery version |
Print version information |
For an auto-reloading dev loop, dev.sh uses
reflex to rebuild and run
go generate && go run *.go serve on every .go change. It detects a running
lbrycrdd and connects via its conf file.
Run the unit tests:
go test -race ./...
There is an end-to-end test that spins up lbrycrd in Docker
(e2e/e2e.sh); it requires Docker. Note the e2e helper still
invokes the legacy docker-compose (v1) CLI.
GitHub Actions runs CI from .github/workflows/ci.yml.
The workflow:
go mod download and go mod verify.go test -race ./....bin/chainquery with scripts/build.sh.The workflow uses Go 1.26.4. The test job runs in the official
golang:1.26.4-bookworm container, and the build/release jobs use GitHub's
hosted ubuntu-24.04 runner. Build tools are pinned in scripts/build.sh so CI
does not silently upgrade to a newer Go toolchain.
Pushing a tag that matches v* publishes a GitHub Release. The release contains
only the Linux amd64 binary:
chainquery-linux-amd64chainquery-linux-amd64.sha256The module depends on github.com/OdyseeTeam/sockety. If the default
GITHUB_TOKEN cannot read that private module, configure a repository secret
named GO_MODULES_TOKEN with access to it.
Local workflow runs use act:
```bash act pull_request \ -W .github/workflows/ci.yml \ -P ubuntu-24.04=catthehacker/ubuntu:act-24.04 \ --secret-file /pat
$ claude mcp add chainquery \
-- python -m otcore.mcp_server <graph>