Skip to content

Latest commit

 

History

History
287 lines (239 loc) · 29.3 KB

File metadata and controls

287 lines (239 loc) · 29.3 KB

Compatibility with Bitcoin Core

Pinned reference version: Bitcoin Core v31.1 (same pin as docs/core-functional.md and nightly fuzz). BIP324 v2 interop. Package wire tracks BIP331 when rust-bitcoin exposes the messages (Q-48 / RB-007).

Experimental 0.x — not a production Core or Fulcrum replacement. Design contrasts: docs/architecture.md. Lab mainnet: docs/experimental-mainnet.md.

A Core, Electrum, or Esplora difference that looks like a bug is often intentional. Read this file before changing shipped behavior. Name the row you are changing; do not mix unrelated dialect work into the same step.

Active product track

Full P2P participant (blocks + tip-mode tx relay) and wallet-client backends: in-process Electrum (confirmed + unconfirmed, libre-relay-class admission) and optional Esplora-compatible REST for the same role (history, UTXO, broadcast, block/tx fetch by id). Optional Core-class JSON-RPC subset (see docs/rpc.md) — not full Core wallet / mining parity. Scripthash index (--shindex) defaults off. Electrum/Esplora start without it; address/scripthash methods fail closed (scripthash index disabled). Txid/outpoint/block/broadcast/fees work. Matrix: docs/lightning.md. On/off costs and start/IBD/tip behavior: OPERATOR.md (Scripthash index). Disable later leaves SH files on disk; follow does not wait on SH materialize.

Query surface intent: wallet clients, plus 0.8 electrs drop-in

Goal: serve wallet software (Electrum, Sparrow, custom wallets, light clients that already know their addresses/scripthashes or exact txids/block ids).

0.8: drop-in mempool/electrs or Blockstream electrs HTTP so nginx /api/ can retire electrs. Core JSON-RPC for that stack is TCP plus an opt-in Core cookie (--rpc-cookie-file), which stock mempool CORE_RPC uses unpatched; a unix-socket patch (filesystem auth) stays optional. mempool.space Node /api/v1/ (MariaDB, cubes, mining, lightning) stays their process. Address-prefix search is not in 0.8 (404). Surface table below.

Non-goal (stays): address-prefix autocomplete, Liquid/assets, in-binary mempool.space catalogue UI (/api/v1/). Opt-in GET /block-template is GBT (same JSON as RPC), not explorer search. Block/tx by full id and address/exact scripthash history exist so wallets, APIs, and (after 0.8) electrs-shaped explorers can verify and sync.

--max-sh-creates N (default 10000; 0 = unlimited) refuses an unpaged Electrum/Esplora SH joins with more than N creates: Esplora HTTP 503, Electrum JSON-RPC error scripthash join exceeds --max-sh-creates. Stats stay full when under the cap.

GET …/txs/summary is a dialect route (not Blockstream Esplora API.md). Shape and owner row: Esplora REST surface.

Product: /tx/:txid/outspend/:vout and /outspends emit Blockstream vin (spending input index) from the schema-22 spent slot. Mempool overlay uses the hub tx’s input index. Unspent remains {spent:false} with no vin. Full /tx/:txid JSON still has vin[]. Electrum has no outspend-vin surface.

Intentional differences

Area This node Bitcoin Core
Chainstore Relational map-free archive (fd tables; see docs/io-modality.md) blocks/undo + LevelDB chainstate
Historical blocks Reconstruct from archive; tip via body queue + peer wire blocks/ blk*.dat
Transport BIP324 v2 only v1 + v2
Mempool structure Cluster graph + chunks Cluster mempool (same lineage)
Admission policy Libre-relay-class (0.1 sat/vB, no dust, full RBF). P2P admits any nVersion. RPC-submit (sendrawtransaction / testmempoolaccept / submitpackage) still rejects "version" outside 1/2. Feerate, RBF, min relay and -blockmintxfee use Core's sigop-adjusted size max(weight, sigop_cost × 20) (--bytes-per-sigop, 0 disables). Cluster and package count/size limits and the block weight budget count raw weight, unlike Core, so the adjustment never becomes a per-tx sigop cap. No 16,000 sigop-cost standardness cap; admission and template selection share a strict 80,000 minus --block-reserved-sigops budget (default reserve 400) Standardness + policy knobs (nVersion 1/2, MAX_STANDARD_TX_SIGOPS_COST 16,000, -bytespersigop)
Compact blocks BIP152 v2 receive + reconstruct + getblocktxn serve. Fill is live mempool + orphanage + extra_compact (cap 100). Outbound extra prefill is on unless --prefill-compact=0 (10 KiB cap, extra-pool last; generate / submit / NewPoWValid pack txs not in the live mempool without delaying forward) v1/v2 high-bandwidth + extra_txn cache (-blockreconstructionextratxn); Core #35558 prefill still unmerged
WTx inventory BIP339 when peer also sends wtxidrelay BIP339
GetAddr Core MAX_ADDR_TO_SEND / MAX_PCT_ADDR_TO_SEND (1000 / 23%), 24h per-bind cache. Named copies in rbitcoin-net — do not “improve” without a named reason to diverge. MAX_ADDR_MAN (8192) is our HashMap DoS cap and must stay above 1000/0.23 Core new/tried buckets (~80k); same 1000 / 23%
Package submit RPC submitpackage / Esplora POST /txs/package (no P2P package command) BIP331 wire
Pruning / GUI Unpruned: seqsigwit.body. --prune-seqsigwit: watermark + NETWORK_LIMITED, kept witness is 288 height files under store/seqsigwit.window/ plus a RAM cap (--prune-seqsigwit-ram-threshold-bytes, 0 = files only). Not a rolling stem. Not Core -prune of headers/txout Supported
Mining template RPC getblocktemplate / getmininginfo / prioritisetransaction (selector; no stratum) GBT + stratum / pool stack
SV2 Template Distribution In-process TDP v2 server over Noise NX (--sv2-tp-listen, default off): NewTemplate / SetNewPrevHash per tip, RequestTransactionData, SubmitSolution → block accept. No templates during IBD; last 3 per session. No plaintext, no pool / JD roles No TDP in Core; sv2-apps bitcoin-core-sv2 bridges Core's IPC mining interface to TDP
Wallets Electrum clients (requires --shindex) Descriptor + legacy
Scripthash index Optional (--shindex, default off); bulk at tip when on External ElectrumX / Fulcrum; Core -txindex is different (txid→block)
JSON-RPC Documented subset (docs/rpc.md); Bearer token or opt-in Core cookie (--rpc-cookie-file), no --rpcuser / --rpcpassword; rbitcoin-cli. --rpc-work-queue defaults to 16 (HTTP occupancy, 503 when full; 0 is that default); a JSON-RPC array is one POST Full Core RPC; -rpcworkqueue is in-flight HTTP jobs (503)
GetData serve Reconstruct/serve 16 (MAX_SERVE_BLOCKS) hashes per inbound message; leftover hashes in that getdata are dropped (RAM cap) Core ProcessGetData can keep serving leftover hashes
Inbound eviction victim After Core-shaped protect (netgroup / recent block / recent tx / min-ping), disconnect the longest-connected remaining inbound Core SelectNodeToEvict youngest in the oldest netgroup
--sptweaks-dust Serve-time floor default 1000 sat (omit P2TR outs value <= floor). 546 matches Cake electrs. Not Cake/Electrum protocol n/a (Electrum tweaks are not Core)
IBD empty-headers EOF Empty headers to our locator + idle path latches headers_done even if a peer advertises a taller less-work height / junk version.start_height. A proven header walk at the tip that every taller peer failed to extend also ends IBD Core header sync follows most-work; advertised start_height is not a remaining header count

Compact reconstruct fills short-ids from the live mempool graph, the orphanage, and a small extra_compact ring (cap 100): RBF-replaced and min-relay-rejected bodies, mempool removals (confirm/evict), inbound cmpctblock prefills, and blocktxn bodies. Coinbase is skipped. That is still not Core’s full -blockreconstructionextratxn cache of every recently seen wire tx. Libre admission (0.1 sat/vB, no dust, full RBF) keeps more bodies live than Core standardness, so a larger extra-txn ring would hit less often than on Core — not never. Eviction still misses the short-id map and costs a getblocktxn when the ring has already rolled off. Growing the ring to Core’s extra-txn shape is worth later; not scheduled (no Open Q-id).

Inbound tx whose prevouts are spent or missing on a confirmed create is MissingPrevout, not an orphan park. INV AlreadyHave is live mempool + orphanage + a recent-confirmed txid/wtxid ring (filled at tip connect and IBD write) + Class A tx_fk_by_txid_tip. Re-delivery of an already-parked orphan still GETDATAs missing parents (TTL) but does not log a second txrelay: park.

Inbound cmpctblock may prefill any well-formed indexes (BIP152). We always log reconstruct fill sources and fetched= blocktxn bytes, and outbound cmpct announce … prefill=N/bytes when we send cmpctblock (tip announce or MSG_CMPCT_BLOCK getdata). Sending extra prefills (beyond coinbase) is on unless --prefill-compact=0. A pending compact owns the first-pass slot bodies (mempool / extra / orphan hits). blocktxn overlays only the missing indexes; apply does not re-walk a live short-id map.

Core-class JSON-RPC (subset)

Per-method notes, auth, and the shindex matrix live in docs/rpc.md. Group status (intentional scope):

Method group Status
Control (help, uptime, stop, getrpcinfo, echo) done (syncwithvalidationinterfacequeue omitted; functional proxy no-op for Core sync_mempools)
Blockchain (getblockchaininfo, getblockcount, getbestblockhash, getblockhash, getblock/header, getdifficulty, getblockstats) done (getblockstats from txstat when stamped; size and count fields match Core, including utxo_increase_actual; omit coins-DB utxo_size_*)
Network (getnetworkinfo, getconnectioncount, getpeerinfo, addnode, disconnectnode, addconnection) done (BIP324 v2-only; getnetworkinfo.version is the fixed Core 0.19 client integer 190000, semver stays in subversion, protocolversion is 70016; peer timeoffset / synced_* from session state; hostname addnode / --connect resolve at dial and retry until live)
Mempool / rawtx (getmempool*, getrawtransaction, sendrawtransaction, testmempoolaccept) done (Libre; RPC maxfeerate / maxburnamount / "version" only)
Coin / MiniWallet (gettxout, scantxoutset) done (scantxoutset is descriptor expansion on --sh-index, not a coins-DB; txouts is always -1)
Index / tips (getindexinfo, getchaintips, waitforblock*) done (txindex = Class A reconstruct)
Fee (estimatesmartfee) done (10-minute inclusion — not Core historical)
Decode (decoderawtransaction, decodescript, validateaddress) done (node subset; official Core dialect scripts stay rpc-dialect)
Regtest generatetoaddress / generatetodescriptor / generateblock / generate / submitblock / setmocktime harness (regtest only except submitblock)
invalidateblock / reconsiderblock / preciousblock done
Mining template (getblocktemplate, getmininginfo, prioritisetransaction, getmempoolcluster) done (no stratum / BIP9 testdummy / wallet keys)
Wallet RPC; createrawtransaction / combinerawtransaction; gettxoutsetinfo never (no UTXO set). The functional harness shims gettxoutsetinfo for rpc_scantxoutset.py

Electrum surface

Method Status Notes
server.version / banner / features done Banner: libre-relay-class. server.version[0] is rbitcoin-electrs <workspace.package.version> — not electrs; see below. server.version negotiates: omitted → 1.6; "1.4" → 1.4; ["1.4","1.4.2"] → 1.4.2; ["1.4","1.6"] → 1.6; "1.4.2-asof" (or a range containing it) → as-of dialect. First call wins. features.protocol_max is 1.6; features.asof_protocol is 1.4.2-asof. features.genesis_hash is display-order hex (refuse a wrong-chain server before a tweaks scan). features.tweaks / silent_payments advertise the method; they are not a substitute for the stream.
blockchain.tweaks.subscribe done Stream, not a one-shot result: JSON-RPC result is the first height only; remaining heights are unsolicited notifications; {"message":"done"} ends the chunk (60s wall at a wave boundary, or the requested count if sooner) — Cake electrs clamps count to 1000 instead. Cake Wallet resubscribes from syncHeight+1 (cake_wallet#3574 persist still lags one event). kiss-bdk today treats done as the asked range finished (kiss-bdk#10). Each height carries tweak + txid + taproot output_pubkeys (client scans locally; no block fetch). Naive walk, or --sptweaks thin index (len:tweak only; one txout span per wave; the first height shares that span when indexed). Pre-taproot: one notify with ≤1024 empty height keys (Cake fromJson last key is progress); probe [0,1,false] stays {"0": {}}. Param [2] (Cake historicalMode): false cut-through (omit confirmed-spent P2TR outs / txs); true keeps spent outs. Serve-time --sptweaks-dust (default 1000) omits P2TR outs with value <= the floor (0 = all; 546 matches Cake electrs sp_min_dust). Sparrow Silent Payments uses Frigate blockchain.silentpayments.subscribe (server-side scan) — not this method. Cake isolate may still hardcode electrs.cakewallet.com.
headers / block headers done Tip push on subscribe
scripthash history / balance / listunspent done Unconf when mempool attached; get_history optional BCH-style from_height / exclusive to_height (-1 = tip + mempool); 1-arg = full history; unconfirmed get_history rows include fee (same as get_mempool); listunspent mempool height is 0 or -1 (unconfirmed parent); subscribe status always full; blockchain.scripthash.unsubscribe returns whether the connection was watching (frees a slot under --electrum-max-subs, default 10000 per connection). listunspent loads txid.body only for unspent creates; one TCP connection reuses the last SH outs+spent join until SH-view hash changes. Confirmed methods stamp live tip: a RAM SH head (pending write-behind) joins with durable SH so mempool can drop confirmed txs without a hole. Durable Class B seed waits until after tip announce. get_history skips mempool rows already in the confirmed list. server.features.chain_tip = true. Trailing asof:<blockhash> after the official args (server.features.asof / asof_protocol = 1.4.2-asof): confirmed rows as of that still-live ancestor at or behind visible SH (durable + pending), no mempool; stamp is the asof block; unknown hash or ahead of visible SH → asof not on chain. Prefix keeps it off the future positional-string landmine. Requires negotiated 1.4.2-asof (first server.version only). Electrum protocol_max is 1.6.
scripthash.get_mempool / subscribe done Status on mempool announce and when SH applies a height that creates or spends the hash (posting-list probe; no Class A expand on a miss). Headers subscribe still live tip. Reorg (TipNotify.reorg_from_height) restatuses every watch even if the new block misses the script. Status preimage is txid:height:blockhash: for confirmed rows (mempool rows stay txid:height:). Row order is confirmed height-asc then mempool tail, same as get_history. A script with no history has status null (subscribe reply and pushes), never "": wallets read any non-null status as a used address. unsubscribe is implemented.
transaction.get / get_merkle done get falls back to mempool unless asof:; confirmed responses stamp chain_tip. Verbose get is electrs-shaped: time/blocktime/confirmations/blockhash from the confirming header (mempool verbose is confirmations: 0 with no block stamp), plus vin/vout/size/version/locktime/hash (wtxid) decoded from the hex already loaded. Trailing asof:<blockhash> (same dialect as scripthash): get returns the tx only if confirmed at or behind that ancestor (no mempool); get_merkle rejects a height above the asof pin (asof not on chain). id_from_pos is a txid string; third arg merkle=true returns {tx_hash, merkle} (same walk as get_merkle).
transaction.broadcast done Mempool accept + P2P inv. broadcast_package wraps accept_package (Electrum 1.6; P2P BIP331 still Q-48).
relayfee / estimatefee / histogram / mempool.get_info done Libre min + live median. mempool.get_info is 1.6 (minrelaytxfee replaces relayfee for 1.6 clients; relayfee stays for 1.4).
outpoint.get_status / subscribe / unsubscribe done Electrum 1.7 methods; protocol_max stays 1.6 until scriptpubkey.*. Spent = confirmed-strong or mempool.
silentpayments.subscribe / unsubscribe done Frigate remote-scanner: session-only scan key; historical + tip notifies via tweak index / naive tweaks_for_height. Not Cake tweaks.subscribe.
TLS external terminate at reverse proxy for clearnet; onion is plain TCP (ADD_ONION to loopback). In-binary 50002 + TLS is parked Q-63.

Protocol versions

features.protocol_max is 1.6 (plus dialect 1.4.2-asof). Electrum 4.8 wallets speak 1.4–1.6; ElectrumX advertises 1.7. block.headers is concatenated hex for 1.4.x and a headers list for 1.6. scriptpubkey.* still missing — do not advertise 1.7 yet.

P2P BIP331 package messages remain Q-48. Local Electrum broadcast_package uses the same accept_package as RPC/Esplora.

Why server.version says electrs

We are not electrs. Cake Wallet getNodeIsElectrs() lowercases version[0] and requires the substring electrs before it will call blockchain.tweaks.subscribe. The first element is therefore rbitcoin-electrs <ver> (ver from workspace.package.version) so Cake will probe tweaks. Other tweaks clients (kiss-bdk) do not need that substring; they discover the chain via server.features.genesis_hash. Cake isolate may still hardcode electrs.cakewallet.com after a passing probe.

Tweaks stream vs Sparrow / Frigate

blockchain.tweaks.subscribe is a client-side BIP-352 scan: the server never sees a scan key. A client that treats the JSON-RPC call as request/response reads one height and stops. Sparrow’s Silent Payments path talks Frigate blockchain.silentpayments.subscribe (scan key on the server, RAM-only for the session). That RPC is implemented on the tweak index (or naive height walk). tweaks.subscribe remains the Cake client-local scan. | DoS floor | always on | max conn / line / idle / subs / broadcast hex (ServeLimits); public bind OK behind proxy |

Chain view (confirmed-tx snapshot token)

Yuval pointed out that Electrum status and Esplora list envelopes are A-B-A: a same-height reorg can leave txid:height (and a height-keyed join cache) unchanged while merkle proofs and confirming block hashes moved. We researched mempool/mempool#6584 (tnull: stamp chain tip hash on every API response header so sequential fetches detect tip movement, including A-B-A) and spesmilo/electrum-protocol#2 (1.7 chaintip on scriptpubkey.*, reverted in #17 because ElectrumX is bitcoind middleware and cannot pin). rbitcoin owns Query+store, so we pin the published tip and retry if it disconnects (docs/concurrency.md).

Surface Token Body
Esplora HTTP X-Bitcoin-Chain-Tip + X-Bitcoin-Chain-Tip-Height Unchanged JSON. Client: if two sequential fetches disagree on the hash, drop the batch and restart.
Electrum TCP JSON-RPC extra members chain_tip / chain_tip_height next to result (ping/version omit). server.features.chain_tip. result shape unchanged. Status preimage includes confirming blockhash so subscribe clients refetch on same-height replace. Notification params stay [scripthash, status].

We stamp tip, not only the last relevant history tx hash (empty history and list envelopes still need a token).

As-of (buried ancestor): thanks again to Yuval — the same A-B-A / bind-confirmations-to-a-chain work implies “wallet as of this block” while that block is still on the best chain. Esplora ?asof=<hash> and Electrum trailing asof:<hash> (after official positional args) join under pin_chain_view_at. Stamp is that hash. If the asof block leaves the tip chain: 404 / asof not on chain (no retry onto another block at the same height). We still do not serve a disconnected fork hash.

Electrum clients that want as-of send server.version(name, "1.4.2-asof") (or a [min, max] range containing that string). Standard Electrum "1.4" / ["1.4", "1.4.2"] stays on dotted-int 1.4.x; an asof: tag without the dialect is an error. server.features.protocol_max remains "1.4.2" so Electrum dotted-int parsers do not choke; discovery is asof + asof_protocol.

Esplora REST surface

Plain HTTP via --esplora-listen / conf esplora_listen (default off). TLS via reverse proxy for clearnet; onion is plain TCP (ADD_ONION to loopback). App ServeLimits always on (same model as Electrum).

Endpoint group Status Notes
Tip done /blocks/tip/height, /blocks/tip/hash. REST stamps X-Bitcoin-Chain-Tip / X-Bitcoin-Chain-Tip-Height (CORS-exposed): live tip for block/tx/header routes; SH watermark for /address/ and /scripthash/ so wallet JSON matches the SH join. Empty chain omits them (existing 503). If the pin dies mid-request: 503 chain view moved.
Blocks list done /blocks, /blocks/:start_height (10 summaries, newest-first)
Block done /block/:hash JSON, /raw, /status, /header, /txids, /txid/:i, /txs[/:start]. JSON bits is the compact-target u32 (Esplora schema, not Core hex). size / weight are BIP144 total size and BIP141 weight (witness included).
Tx done /tx/:txid full JSON, /hex, /raw, /status, Electrum /merkle-proof, BIP37 /merkleblock-proof, /outspend(s) (vin from the spent slot; unspent omits it). Below seqsigwit prune: JSON is 200 partial (pruned: true, vout+status, empty vin); stamped txstat fills fee/size/weight. Mempool-only txs (not in Class A) use the wire body from the mempool hub (vin/vout/size/weight/fee/sigops, status.confirmed false) including GET /tx/:txid/status. Live /outspend(s) overlay mempool spends of confirmed coins; ?asof= omits mempool. ?asof=<hash> on /status and /outspend(s): confirmed/spent as of that ancestor; 404 if not on chain.
Address / scripthash done stats + /utxo + /txs + /txs/mempool + /txs/chain[/:last_seen_txid] + /txs/summary[/:last_seen_txid] (dialect; next row). /utxo matches Electrum listunspent (mempool funding + drop mempool-spent confirmed); /txs and /txs/mempool use full Esplora tx JSON for mempool-only rows (wire from the hub). ?after_txid= on /txs and /txs/summary skips through that tx (mempool then chain); unknown or unparseable → 422 after_txid not found. POST /addresses/txs and POST /scripthashes/txs (and /txs/summary) take a JSON array (max 300; over → 422 body too long); unique scripts join once per request, merge newest-first. Unix/loopback X-Rbitcoin-Client: last-1 GET join + last-bulk POST (16 MiB packed/client), 30s idle, 256 clients, singleflight on the same (client, sh). Public TCP ignores the header (stack-local join). Concurrent different GET scripts replace last-1. Unbounded process LRU stays X-M3. Needs SH finalize. Stamp is visible SH (durable + pending write-behind), matching live tip while jobs sit in RAM. ?asof=<hash> on /, /utxo, /txs, /txs/chain, /txs/summary: confirmed join at that ancestor at or behind visible SH, no mempool; headers are the asof hash; 404 if not on chain or ahead of visible SH.
/txs/summary dialect Not in Blockstream Esplora API.md. Compact {txid, value, height, time, tx_position} like mempool.space /address/:addr/txs/summary. Confirmed only (25/page, newest first); path cursor /:last_seen_txid and mempool.space ?after_txid= (unknown → 422). value is net sats for that script in that tx (funded − spent). time is the confirming header timestamp (0 if the header is missing). tx_position is that tx's index in the block. Mempool rows stay on /txs and /txs/mempool. A page is served above --max-sh-creates; unpaged stats and /utxo still 503.
Mempool / fees done /mempool, /mempool/txids, /mempool/txids/page[/:last], /mempool/recent (accept-order ring), /fee-estimates (sat/vB at the estimator's 1 sat/kvB resolution; a target without an estimate is left out, and 503 fee estimates unavailable when none has one). mempool.space /api/v1/fees/recommended is its backend's surface, not served here (404)
POST /tx done broadcast via mempool hub; 503 if hub absent. GET /broadcast?tx= is the same admit path (legacy electrs).
POST /txs/test done JSON array of hex (max 25) → MempoolHub::test_accept (no admit). ?maxfeerate= is BTC/kvB like electrs (0 unlimited; omitted → 0.1 BTC/kvB).
POST /txs/package done JSON array of hex txs → accept_package; 503 without hub; max 25 txs
electrs /internal/* done Unix listen only. TCP GET/POST /internal/… → 404 (same unknown-path fallback). POST /internal/txs (400 on unparseable id; missing omitted); POST /internal/mempool/txs (mempool only); GET /internal/mempool/txs[/all or /:last] (txid-sort pages, default max_txs=10000; /all registered first); GET /internal/block/:hash/txs (full list; public /txs stays 25/page); POST /internal/txs/outspends/by-txid (same-length slots, unknown → []); POST /internal/txs/outspends/by-outpoint (txid:vout; malformed → {"spent":false}). Snapshot JSON is published on the hub (no admit-path build). GET /mempool/txids/page is public electrs (both listeners).
Unix listen done --esplora-listen filesystem path (mode 0660). Public REST plus /internal/*. TCP --esplora-listen host:port never mounts /internal.
Unknown path 404 plain body (including /address-prefix)
GET /block-template opt-in --esplora-block-template (default off → 404). Same JSON as RPC getblocktemplate {"rules":["segwit"]} template mode. 503 without tip. Cache-Control: no-store. 15 s cache, invalidated on tip or mempool template_updates. No proposal/longpoll HTTP (parked Q-64).
Non-goal / never — Address-prefix search, Liquid/assets. mempool.space /api/v1/ catalogue stays their Node.

No Esplora WebSocket

Esplora and electrs have no WebSocket. mempool.space's /api/v1/ws (want, track-address, track-tx, …) is its backend's surface, so /ws and /v1/ws are 404 here. Wallets watching live use Electrum subscriptions, or mempool's backend in front of this Esplora.

BIP324 v2 short-ID surface (live paths)

Encode/decode uses Core’s V2_MESSAGE_IDS table (crates/rbitcoin-net/src/v2.rs). Live IBD + tip follow + tip tx relay commands with short IDs:

Short ID Command Role
1 addr peers
2 block IBD / tip body
3 blocktxn compact fill
4 cmpctblock tip HB
5 feefilter tip policy
9–15 getblocks…mempool headers/blocks/inv
17–21 notfound…tx ping/pong/sendcmpct/tx
22–27 getcfilters…cfcheckpt BIP157 basic while --block-filter-index is on. A stop past the filter watermark is silence; an out-of-range request disconnects (Core)
28 addrv2 BIP155

Long-form (no short ID): version, verack, wtxidrelay, sendheaders, sendaddrv2, and unknown/extension commands.

Not implemented as product features (short slots 29–36 placeholders, 37 feature): decode may reject unknown short IDs. Slots 22–27 are live for BIP158 basic filters while --block-filter-index is on (NODE_COMPACT_FILTERS once filters first reach the tip, getcfilters, getcfheaders, getcfcheckpt, getblockfilter). A request whose stop height is past the filter watermark is silence, not an empty filter or a short batch. As in Core's PrepareBlockFilterRequest, start past stop, 1000+ getcfilters, or 2000+ getcfheaders heights disconnects the peer.

Deferred surfaces

Core wallet RPC, fee-estimator research quality, BIP331 native wire enum, durable orphans: out of scope for this plan. GBT template RPC is shipped (see above); stratum / pool software is not.

Permanent non-goals for Electrum/Esplora: address-prefix autocomplete, Liquid/assets, in-binary mempool.space /api/v1/ (MariaDB cubes / mining / lightning). 0.8 is electrs HTTP drop-in except prefix. Core RPC for that stack is TCP plus an opt-in Core cookie (--rpc-cookie-file, stock mempool); the unix-socket patch is optional.