Skip to content

Support Trezor's Blockbook as a datasource #163

Description

@b10claude

Blockbook is Trezor's blockchain indexer and block explorer backend. It sits in
front of a Bitcoin Core node and exposes a REST + WebSocket API (/api/v2/…).
Like Esplora and block-dn it has no getchaintips-like endpoint, so we would
only get its single active tip - useful alongside at least one Bitcoin Core or
btcd node on the same network.

What the API gives us

Verified against a public mainnet instance (https://bitcoin.atomicwallet.io,
Blockbook devel / build 2026-07-29, backend /Satoshi:31.1.0/). The
*.trezor.io instances are behind Cloudflare and block non-browser clients, so
they can't be used for testing (or as a configured backend) without a browser
user agent.

Node trait method Blockbook endpoint
tips() GET /api/v2blockbook.bestHeight + backend.bestBlockHash
version() GET /api/v2blockbook.version and/or backend.subversion
block_hash(height) GET /api/v2/block-index/<height>{"blockHash": "…"}
block_header_hash(hash) GET /api/v2/block/<hash> (header fields, see below)
coinbase(hash) GET /api/v2/block/<hash>txs[0].txid, then GET /api/v2/tx-specific/<txid>hex
block(hash) GET /api/v2/rawblock/<hash>{"hex": "…"}
batch_header_fetch() not available

Headers have to be rebuilt from the block JSON

Blockbook has no header endpoint, but /api/v2/block/<hash|height> returns all
six header fields (api.BlockInfo): version (JSON number), previousBlockHash,
merkleRoot, time, bits (compact hex), nonce (decimal string). Rebuilding
the 80-byte header from those and checking that it hashes to the requested hash
works - checked field by field against the first 80 bytes of
/api/v2/rawblock/962050:

JSON:  version 537608192, prev …4815eda1b, merkle 1af428e7…, time 1786478836,
       bits "1702353d", nonce "500239977"
raw:   00400b20 1bda5e81…  60048642…  f4807b6a 3d350217 690ed11d   ✓

That is the same pattern MempoolSpace already uses (header_from_mempool_space_block
verifies the header hashes to the hash we asked for), except mempool.space hands
out the serialized header directly and here we assemble it.

The catch: a header costs a page of transactions

/api/v2/block/<hash> always ships up to 1000 full transactions with the header
fields; page is the only knob (pageSize is fixed at 1000 server-side), and
asking for a page past the end clamps to the last one instead of returning an
empty tx list. Measured on block 962050 (3167 txs): 1.1 MB for page=1, 161 kB
for the last page (167 txs). So the cheapest header fetch is
?page=<large> at roughly 1 kB per transaction on the last page - on average a
few hundred kB per header. (Blockbook does serve gzip - 40 kB for that 161 kB
response - but minreq doesn't decompress, so we'd pay the full size.)

That rules out backfilling and makes this a tip-following backend, same
reasoning as mempool.space: batch_header_fetch: false, run it next to a
Bitcoin Core node that supplies the active chain so new_active_headers stops
at the first header it already knows, and keep min_fork_height close to the
tip. Worth spelling out in the README like the mempool.space note does.

Other findings

  • Blockbook only indexes the main chain: /api/v2/block/<stale hash> answers
    400 {"error":"Block not found"}, so no fork branches, no stale blocks from
    block(). partial_nonactive_headers: true is harmless insurance but never
    kicks in, since our own tips() only reports the active tip.
  • /api/v2/block-index/<height above tip> answers 200 {"blockHash":""} rather
    than an error - needs an explicit empty check.
  • /api/v2/rawtx/<txid> is not on every deployment (the instance above falls
    through to the status handler), while /api/v2/tx-specific/<txid> (bitcoind's
    getrawtransaction verbose output, including hex) is there and portable.
    Prefer the latter for the coinbase.
  • Pool identification works: we can get the raw coinbase, unlike Electrum.
  • Optional follow-up: Blockbook's WebSocket (/websocket) has
    subscribeNewBlock, which would fit wait_for_tip_change() and avoid polling.
    Needs a WebSocket dependency, so probably not in a first version.

Sketch

  • src/backend/blockbook.rs with a Blockbook struct (info, api_url),
    HeaderFetchType::Hash, batch_header_fetch: false
  • Backend::Blockbook in src/config.rs ("blockbook"), FetchError::BlockbookREST
  • rpc_host = "https://<host>/api/v2", following the Esplora style
  • README section + config.toml.example entry, incl. the backfill limitation

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions