Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions COMPAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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`](./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. Which wallets use that surface: [`docs/wallets.md`](./docs/wallets.md). Lightning chain sources: [`docs/lightning.md`](./docs/lightning.md).
**Scripthash index (`--sh-index`) defaults off**. Electrum/Esplora **start without it**; address/scripthash methods fail closed (`scripthash index disabled`). Txid/outpoint/block/broadcast/fees work. Which wallets use that surface: [`docs/wallets.md`](./docs/wallets.md). Lightning chain sources: [`docs/lightning.md`](./docs/lightning.md).
On/off costs and start/IBD/tip behavior: [`OPERATOR.md`](./OPERATOR.md)
(Scripthash index). Disable later leaves SH files on disk; follow does not
wait on SH materialize.
Expand Down Expand Up @@ -73,12 +73,12 @@ Full `/tx/:txid` JSON still has `vin[]`. Electrum has no outspend-vin surface.
| 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, `NewTemplate` on a same-tip fee gain (`--sv2-tp-fee-delta`, `--sv2-tp-template-interval`, sv2-tp defaults), `RequestTransactionData`, `SubmitSolution` → block accept. No templates during IBD; every template on the tip kept, up to 64 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) |
| Wallets | Electrum clients (requires `--sh-index`) | Descriptor + legacy |
| Scripthash index | Optional (`--sh-index`, default **off**); bulk at tip when on | External ElectrumX / Fulcrum; Core `-txindex` is different (txid→block) |
| JSON-RPC | Documented **subset** ([`docs/rpc.md`](./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 | Every hash is served in order. While **16** (`MAX_SERVE_BLOCKS`) getdata bodies or 4 MiB sit in the peer's queue (RAM cap), the rest waits on the session and no new message is read; none is dropped. Pings and the ping timeout still run | Core `ProcessGetData` pauses on `fPauseSend` and serves the rest before the next message |
| Inbound eviction victim | After Core-shaped protect (netgroup / recent block / recent tx / min-ping / 8 longest-connected), disconnect the **newest** inbound in the largest remaining netgroup. A fully protected set rejects the new 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) |
| `--sp-tweaks-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
Expand Down Expand Up @@ -121,7 +121,7 @@ Per-method notes, auth, and the shindex matrix live in
| 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`) |
| Regtest `generatetoaddress` / `generatetodescriptor` / `generateblock` / `generate` / `submitblock` / `setmocktime` | harness (`generate*` also on signet whose challenge is exactly `OP_TRUE`; `setmocktime` / `mockscheduler` stay regtest; `submitblock` is all networks) |
| `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` |
Expand All @@ -131,13 +131,13 @@ Per-method notes, auth, and the shindex matrix live in
| 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](https://github.com/cake-tech/cake_wallet/issues/3574) persist still lags one event). [kiss-bdk](https://github.com/kkdao/kiss-bdk) today treats `done` as the asked range finished ([kiss-bdk#10](https://github.com/kkdao/kiss-bdk/issues/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`. |
| 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](https://github.com/cake-tech/cake_wallet/issues/3574) persist still lags one event). [kiss-bdk](https://github.com/kkdao/kiss-bdk) today treats `done` as the asked range finished ([kiss-bdk#10](https://github.com/kkdao/kiss-bdk/issues/10)). Each height carries tweak + txid + taproot `output_pubkeys` (client scans locally; no block fetch). Naive walk, or `--sp-tweaks` 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 `--sp-tweaks-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 | `minrelaytxfee` is the configured minimum. `relayfee` and `mempoolminfee` are the enforced floor. `incrementalrelayfee` stays 100 sat/kvB. Estimates still use the live median. `mempool.get_info` is 1.6 (`minrelaytxfee` replaces `relayfee` for 1.6 clients; `relayfee` stays for 1.4). |
| relayfee / estimatefee / histogram / `mempool.get_info` | done | `minrelaytxfee` is the configured minimum. `relayfee` and `mempoolminfee` are the enforced floor. `incrementalrelayfee` stays 100 sat/kvB. Estimates are the 10-minute flow/history product ([`docs/mempool-fee-estimation.md`](./docs/mempool-fee-estimation.md)). `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**. |
Expand Down Expand Up @@ -210,9 +210,9 @@ 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`.
without the dialect is an error. `server.features.protocol_max` stays
`"1.6"`. As-of discovery is `asof` / `asof_protocol` (`1.4.2-asof`), not
`protocol_max`.

## Esplora REST surface

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ does not apply to them.
Humans who want the same gates offline (Nix optional; rustup 1.95 is enough):

```bash
# nix develop # Linux only — pin via flake.lock; or rustup + rust-toolchain.toml
# nix develop # Linux and aarch64-darwin — pin via flake.lock; or rustup + rust-toolchain.toml
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$PWD/target/dev}"
cargo fmt --all -- --check # includes nested fuzz/ crate
# rustc warnings are denied via workspace.lints (+ RUSTFLAGS=-Dwarnings in the Nix shell)
Expand Down
21 changes: 12 additions & 9 deletions SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ Older versions and migration notes live in [`SCHEMA_HISTORY.md`](./SCHEMA_HISTOR
## Schema 17 freeze

Class A shape settled here at schema 17 and is still the live layout under
the `SCHEMA_VERSION` at the top of this file (25). Schemas 18–24 already
shipped; they are open rules above and history in
the `SCHEMA_VERSION` at the top of this file (26). Schemas 18–26 already
shipped. Meta 22–26 is what this binary opens (rules above). Earlier
layouts are history in
[`SCHEMA_HISTORY.md`](./SCHEMA_HISTORY.md). A byte-incompatible change bumps
from that live constant
([Changing durable bytes](#changing-durable-bytes)). It does not target
Expand Down Expand Up @@ -230,7 +231,7 @@ itself changed.
tx.body / tx.idx.* # schema ≤14 packed (refused if non-empty)
txid.body # dense create_fk-ordered txids (schema 13+)
txstat.body / txstat.ovf / txstat.blk # 8 B/create ULEB econ + per-header tails (schema 25)
input.loc / input.off / input.body # spender → parent edges (schema 22–23)
input.loc / input.off / input.body # spender → parent edges (8 B/input) + n_in (2 B/create)
tx.head/ # meta + open OA NNNNNN; sealed NNNNNN.mphf|.fuse8
spent.ovf # multi-spender overflow (was spenders.body)
confirmed.body # Class C: height → header_fk
Expand Down Expand Up @@ -284,7 +285,7 @@ store. `txstat`, `input`, block filters, and tweaks are not pruned.
| Offset | Size | Field |
|--------|------|-------|
| 0 | 4 | Magic `RBT1` |
| 4 | 2 | Schema version (u16) — live **25** (`SCHEMA_VERSION`). Occupied files keep the version they were written; **13–25** remain `schema_file_openable` |
| 4 | 2 | Schema version (u16) — live **26** (`SCHEMA_VERSION`). Occupied files keep the version they were written; **22–26** remain `schema_file_openable` |
| 6 | 2 | Table kind (u16) |
| 8 | 8 | Logical length (bytes), including this header |

Expand Down Expand Up @@ -314,6 +315,7 @@ store. `txstat`, `input`, block filters, and tweaks are not pruned.
| 21 | txstat per-header locator (`txstat.blk`, 16 B/header) |
| 22 | inputs (`input.body`, 8 B/input) |
| 23 | inputs loc (`input.loc`, 2 B/create `n_in`) |
| 24 | blockfilter (`blockfilter.body`) |

---

Expand Down Expand Up @@ -609,7 +611,7 @@ Contiguous assignment required: block membership is an arithmetic range.
### Optional BIP-352 thin tweaks (`sp_tweaks.*`)

Schema **17** side product. Soft-open: missing dirs are empty (not `Corrupt`,
not a head recreate). Created when `--sptweaks` is on.
not a head recreate). Created when `--sp-tweaks` is on.

**Tip / strong height only** — no `header_fk` in the idx. A reorg truncates
above the new tip and those heights are written again. `put` requires
Expand All @@ -629,7 +631,7 @@ slot in that file, or that file’s HWM). `n_tx` comes from

Leftover **files** `store/sp_tweaks.idx` and `store/sp_tweaks.body` (schema 14
single-file, `header_fk` + absolute off) are unlinked on store open.
`--sptweaks` backfill regenerates. Not a Class A wipe.
`--sp-tweaks` backfill regenerates. Not a Class A wipe.

Reorg: truncate slots above the new tip (same era as SH HWM).

Expand Down Expand Up @@ -860,9 +862,10 @@ Schema 15 leftover `tx_height.body` is unlinked on open (logged).

### Commit order (confirm)

1. `strong_tx` (may lead tip after kill)
2. Thin scripthash creates (may lead tip)
3. **`confirmed[]` tip advance** ← **commit**, then fence extend
1. `strong_tx` (may lead tip after kill, before the barrier)
2. Height-fence extend (RAM), then **`confirmed[]` tip advance** ← **commit**
3. `flush_class_c_tip` (headers, then `strong_tx`, `header_txs`, `confirmed[]` last)
4. Scripthash write-behind enqueue (after tip). Direct IBD defers SH until tip entry

`is_confirmed_strong(tx)` ⇔ strong ∧ fence contains the fk (implies height ≤ tip
and membership in `confirmed[h]` header_txs).
Expand Down
Loading
Loading