A BSV block-header service on Cloudflare Workers, with D1 header storage, R2
bulk export and a Durable Object for tip-stream ingestion. 0.5.0 is the
version in Cargo.toml; the crate is bsv-chaintracks-cloudflare
and its library keeps the name rust_chaintracks. The header frame, compact
target, proof-of-work check, chain work and the difficulty rule come from
bsv-rs 0.7.2 (exact); the checkpoints and the store's ancestry window are in
src/consensus.rs.
Headers pass proof-of-work, difficulty, checkpoint and ancestry checks before they can be served as the active chain. Merkle-root queries use that verified header view. During revalidation, the served tip is limited to the validated cursor. src/storage.rs owns insertion and chain selection; src/routes.rs owns the query and admin routes.
The E5 push source is the TipStream Durable Object in
src/tip_stream.rs. It reads a peer's tip stream, checks
announced headers through src/push.rs, and retrieves missing
parents through the courier ladder in src/couriers.rs.
The minute cron wakes the object and polls when push coverage is absent or
quiet. A courier supplies bytes, not authority over chain selection.
Consumers can read the persisted chain-event feed through polling and SSE;
docs/CHAIN-EVENTS.md defines the envelopes, cursor and
reconnect behavior; docs/SPLITS.md defines the version-2
split and splitResolved events. The service also provides header/root
queries (/findHeaderHexForHeight, /isValidRootForHeight and the
/chaintracks/v2 and /v2 views), bulk sync, link checks and controlled
repair routes under /admin/. It does not require clients to poll the whole
chain. /getInfo carries rungTips, each configured courier's latest tip.
Three times answer three questions, each from the single sync_state row:
| field | moves when | means |
|---|---|---|
lastSyncedAt |
a new tip's claim (audit M6) | the last advance |
lastSeenAt |
a cron with work; an idle cron only when the record is behind the couriers' answer | the couriers' highest answer |
lastPassAt |
the end of every completed sync pass, idle passes and ticks the push covers included (migration 0013) | the liveness signal: a pass ran |
On a quiet chain lastSyncedAt ages while lastPassAt stays a minute old; a
stalled tracker ages both. A pass no courier answered, or one that failed,
records lastSyncError and lastSyncErrorAt and leaves lastPassAt.
The load: one UPDATE of the sync_state row a pass, 1,440 D1 writes a day
at the minute cron, bounded by the cron and not by the chain. It is the one
deliberate exception to "the idle cron writes nothing" (round 5, LOW-4), which
still holds for the courier record and the tip announce; the minute's age
writes of the chain-event feed are separate. /getInfo reads the stamp with
one more read of the same row; before migration 0013 it omits lastPassAt
and names the missing column in syncSchemaFault, and the cron still syncs.
The deploy's [vars] and secrets that shape the feed and the header routes
(the rest are in wrangler.toml):
| name | kind | unset | set |
|---|---|---|---|
CHAIN_EVENT_FEED |
var | the 0.6.0 feed | "2": no minute tipAge, one query a stream wake and a clean end at 900, the 90-day retention and 410 cursorExpired, the outbox's one head read (docs/CHAIN-EVENTS.md) |
HEADER_EDGE_DEPTH |
var | no cache; 0.6.0 lifetimes more than 400 below the tip, no-cache within |
the settled line (at least 400): settled answers from caches.default for a day, tagged |
HEADER_EDGE_ZONE_ID |
var | no zone purge (the colo's cache.delete only) |
the zone whose tags a purge names |
HEADER_EDGE_PURGE_TOKEN |
secret | as above | the purge token, read by env.secret alone |
CHAIN_EVENT_WEBHOOK_URLS, CHAIN_EVENT_WEBHOOK_TOKEN |
var, secret | no outbox | the outbox targets and their bearer |
CHAIN_EVENT_STREAM_BOUND |
var | 900 | a test hook: a lower stream bound, never a higher one |
GET /health returns the service status plus build.lib_rev, build.dirty,
build.built_at and build.crates (the package version under the key
rust-chaintracks, and bsv-rs), plus the checks.rungSpan configuration
check. It does no database or outbound work. It identifies the compiled artifact;
/getInfo supplies readiness, validation and lag information.
wrangler.toml includes a separate beta environment with its own D1, R2 and Durable Object bindings. docs/BETA.md covers private configuration, migrations, bootstrap, readiness and the public-tag build. Build and deploy from a clean public release checkout, configure resources privately, apply migrations, seed the header store and check validation and lag before treating beta as ready. Deployments remain operator actions.
With Rust 1.95.0, its wasm target and worker-build 0.7.5 available, the cargo gates ci.yml runs are:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --target wasm32-unknown-unknown --lib -- -D warnings
cargo test --no-fail-fast
worker-build --release.github/workflows/ci.yml runs test, fmt, clippy,
wasm32 and worker on PR heads and main pushes. It also checks host clippy
and generates the chain-event witness before running every compiled-Worker
script. Those scripts in tests cover bootstrap, link repair, E5 push,
event/SSE delivery, health and the outbound fallback rules. The event test
requires the generated witness; the bootstrap test obtains bulk header fixtures.
Host tests alone do not establish the compiled Worker's behavior.
The tag (v0.4.3 and on) is the release of record. From this release on, the
crate bsv-chaintracks-cloudflare on crates.io carries the same version as the
tag: .github/workflows/release.yml checks the
tag against Cargo.toml and its ancestry on main, runs the CI gate, then
publishes through crates.io trusted publishing. The Worker in production is
deployed from the tag by the stack's infra, not by this workflow. There are no
earlier crates.io entries under this name or the old rust-chaintracks.
CHANGELOG.md records changes, docs/A3-32-REPORT.md records event-feed witnesses, and src/testdata/README.md describes header fixtures. See LICENSE.