An independent Bitcoin P2P indexer for ION/Sidetree OP_RETURN anchors.
ion-node connects directly to the Bitcoin P2P network (no bitcoind, no Neutrino, no third-party API), syncs headers-first, downloads full blocks, and maintains a reorg-safe, resumable index of ION anchors ordered by anchor-time. It is the read/index foundation of a larger node; DID resolution and operation publishing are later milestones.
| Component | Package | State |
|---|---|---|
| Config / profiles / logging | internal/config, internal/log |
✅ |
| Chain interfaces + btcd adapters + merkle verify | internal/chain |
✅ |
| OP_RETURN anchor scanner | internal/scan |
✅ |
| Reorg-safe store (SQLite default, bbolt optional) | internal/store |
✅ |
| Validated most-work header chain | internal/chain |
✅ |
| Custom btcd P2P client | internal/p2p |
✅ |
| Indexer pipeline (sync / reorg / resume) | internal/index |
✅ |
CLI (sync/start/status) |
cmd/ion-node |
✅ |
Future: M2 — read/resolution (fetch Sidetree files from IPFS via an external daemon behind sidetree-go's CAS interface, replay DID operations, expose resolution). M3 — write/anchor (build Sidetree files, publish to IPFS, construct + broadcast the anchoring Bitcoin transaction).
ion-node depends on the private github.com/13x-tech/ion-sdk-go (and sidetree-go), and on a fork of go-jose for secp256k1. Both are handled, but you must set GOPRIVATE:
export GOPRIVATE=github.com/13x-tech/*
go build ./...
go test ./...The go.mod carries the required replace github.com/go-jose/go-jose/v3 => github.com/13x-tech/go-jose/v3 directive. Requires Go 1.23+.
# Print the resolved configuration for a network profile.
ion-node --network regtest config
# Sync headers and index up to the stable frontier once, then exit.
ion-node --network mainnet --datadir ./data sync
# Run continuously (re-syncs on an interval) until Ctrl-C.
ion-node --network mainnet --datadir ./data start
# Print committed index status from the local store.
ion-node --network mainnet --datadir ./data statusNetwork profiles: mainnet (public ION: prefix ion:, genesis 667000), testnet (genesis 1764000), signet, regtest, simnet. Any field is overridable (--prefix, --genesis, --confirmations, --max-reorg, --peer, --anchor-peer, --store, …).
The anchor index sits behind a Store interface with two interchangeable backends, selected with --store:
sqlite(default) — pure-Gomodernc.org/sqlite(no CGO), WAL mode. Chosen because later milestones query the index (by CID, txid, DID-suffix, confirmations, a resolution API), which SQL serves with free secondary indexes; reorg rollback isDELETE WHERE transaction_number >= ?with indexes unwinding automatically.bolt—go.etcd.io/bboltB+tree, the original embedded ordered-KV backend.
Both implement identical semantics (whole-height replace, prevHash self-validation, gap-proof watermark, atomic rollback) and are parity-tested — including a differential fuzz test that runs random commit/rollback scripts against both and asserts they never diverge.
DNS seeds ─▶ btcd peer/connmgr ─▶ headers-first sync ─▶ HeaderChain (PoW + retarget + most-work)
│ stable frontier = tip − confirmationDepth
▼
full-block download ─▶ merkle verify ─▶ OP_RETURN scan ─▶ reorg-safe store (SQLite)
│ (txNumber = height·2³²+txIndex)
▼
M2: ion-sdk-go / sidetree-go resolution (future)
- Headers-first, most-work fork choice. Each header is checked for raw proof-of-work, then difficulty/retarget/timewarp via btcd's
CheckBlockHeaderContext(reused, not hand-rolled). The active chain is the one with the most cumulative work. - Commit only the stable zone (
height ≤ tip − confirmationDepth, default depth 6). The committed index trails the tip, so it never races near-tip churn. Reorgs shallower than the depth are absorbed by the header loop; reorgs between the depth andmaxReorgDepth(default 100) trigger an atomic rollback + reprocess; deeper reorgs fail-stop (see below). - Whole-height atomic commits keyed by
transactionNumber. Reorg rollback is a single range delete; the watermark never advances over a gap, so restarts resume from the last contiguous height.
ion-node is an SPV-style indexer: it validates the most-work header chain (PoW + difficulty/retarget) and per-block merkle inclusion, but does not run full Bitcoin consensus (no UTXO set, no script/signature validation). It is anchored to a configurable minimum-chain-work floor + checkpoint + a reserved anchor peer / /16 peer-diversity floor.
This is sufficient to establish that the scanned OP_RETURN transactions really exist in the most-work chain. It does not protect against: invalid-but-well-formed blocks buried under real PoW (you trust miners for in-block validity), majority-hashpower rewrites below the finality depth (mitigated only by confirmationDepth), or eclipse attacks (mitigated by peer diversity + the min-work floor). Sidetree per-operation-fee / value-locking validity is not enforced. JWS signature verification of operations happens in ion-sdk-go's replay engine (M2), not here.
A reorg deeper than maxReorgDepth fails stop (the store is left untouched, no silent corruption) and requires an operator-triggered resync from a safe height.
Apache-2.0 © 13x LLC.