Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
2057f51
docs(consensus): the mirror admission gate header names its own file
jdogresorg Sep 17, 2026
bc8f764
docs(consensus): restore snapshot reorg buffer comments
jdogresorg Sep 17, 2026
914825c
fix(consensus): re-cut the LTC and DOGE mirror-admission heights onto…
jdogresorg Sep 17, 2026
751b47f
fix(mirror): stream match anchor stamps as metadata
jdogresorg Sep 17, 2026
3a0cf02
Require sibling guards in full hub CI
jdogresorg Sep 18, 2026
2a42728
Merge master into develop after the v0.20.0 train
jdogresorg Sep 19, 2026
a3bc6a2
fix(consensus): propagate the admission boundary fix to the hub twin
jdogresorg Sep 19, 2026
6dfd0f1
Revert "fix(consensus): propagate the admission boundary fix to the h…
jdogresorg Sep 19, 2026
66e63de
fix(protocol): ship LTC:testnet mirror admission as null under dq4 (a)
jdogresorg Sep 19, 2026
2d0ff72
fix(consensus): re-slide v0.20.1 BTC and DOGE activation heights
jdogresorg Sep 19, 2026
1ffdb10
feat(oracle): configure derivation tick budget
jdogresorg Sep 20, 2026
a5d6965
fix(attestation): bound marker hydration SQL
jdogresorg Sep 20, 2026
a0ef847
Land row L002-94 from cx-l002-94
jdogresorg Sep 20, 2026
77a682d
fix(attestation): preserve bounded marker hydration
jdogresorg Sep 20, 2026
408699e
Land row 59 from cx-oracle-59
jdogresorg Sep 20, 2026
c88bd37
fix(oracle): route derive budget through config
jdogresorg Sep 20, 2026
3a4bd74
Land row L002-94 from cx-l002-94
jdogresorg Sep 20, 2026
ca69973
Land row L011-59 from cx-oracle-59
jdogresorg Sep 20, 2026
3fe086c
test(oracle): rename PRICE v0 batch parity suite
jdogresorg Sep 20, 2026
d62710b
Add proposal-scoped admission tip memoization
jdogresorg Sep 20, 2026
e2a63a5
test(attestation): pass canonical widening separately
jdogresorg Sep 20, 2026
61e6bc6
Purge regtest relics on BTC chain identity change
jdogresorg Sep 20, 2026
cafa6e4
Learn hub network from pushed config
jdogresorg Sep 20, 2026
5333f01
Land rows L002-95 and L002-96 from cx-l002-95 and cx-l002-96
jdogresorg Sep 21, 2026
dfaf6ce
test(attestation): move the catch-up horizon case to its own file
jdogresorg Sep 21, 2026
d268635
fix(anchor): clamp admission trail TTLs
jdogresorg Sep 21, 2026
73f9c57
feat(oracle): configure derivation tick budget
jdogresorg Sep 20, 2026
21502c2
fix(attestation): preserve bounded marker hydration
jdogresorg Sep 20, 2026
666e2f3
fix(oracle): route derive budget through config
jdogresorg Sep 20, 2026
9185190
Land rows L002-95 and L002-96 from cx-l002-95 and cx-l002-96
jdogresorg Sep 21, 2026
3657c99
test(attestation): move the catch-up horizon case to its own file
jdogresorg Sep 21, 2026
98ac256
Re-vendor the attest batch wire twin after admit_block_btc
jdogresorg Sep 21, 2026
9394ab2
Land row L004-21 from cx-l004-21
jdogresorg Sep 21, 2026
ba475fe
Land row L004-25 from cx-l004-25
jdogresorg Sep 21, 2026
1521086
Land row L008-26 from cx-l008-26
jdogresorg Sep 21, 2026
ff00268
Land row L010-13 from cx-l010-13
jdogresorg Sep 21, 2026
bac0088
Land row L011-52 from cx-l011-52
jdogresorg Sep 21, 2026
093b917
Land row L004-D2 from cx-l004-d2
jdogresorg Sep 21, 2026
b6d78aa
Revert "Land row L011-52 from cx-l011-52"
jdogresorg Sep 21, 2026
bfea780
Learn hub network from pushed config
jdogresorg Sep 21, 2026
40e9053
Land row L011-52-R from cx-l011-52-r
jdogresorg Sep 21, 2026
c408f26
fix(consensus): re-land the mirror admission boundary correction in t…
jdogresorg Sep 21, 2026
e44098f
Merge: land row L007-2, the hub twin of the mirror admission boundary…
jdogresorg Sep 21, 2026
28c0acd
fix(tooling): consensus-identity rejects an unknown flag with exit 2
jdogresorg Sep 21, 2026
6eda949
build(hub): nest ajv 6 overrides under eslint and @eslint/eslintrc so…
jdogresorg Sep 21, 2026
e7bc9fd
Merge: build(hub): nest ajv 6 overrides under eslint and @eslint/esli…
jdogresorg Sep 21, 2026
468192f
Merge: fix(tooling): consensus-identity rejects an unknown flag with …
jdogresorg Sep 21, 2026
6565577
fix(attestation): claim a batch window atomically before broadcasting
jdogresorg Sep 21, 2026
67617e4
Merge: fix(attestation): claim a batch window atomically before broad…
jdogresorg Sep 22, 2026
003c337
fix(anchor): name TTL envelope clamps
jdogresorg Sep 22, 2026
b647105
fix(health): report stale admission tips
jdogresorg Sep 22, 2026
677f676
Merge: fix(anchor): name TTL envelope clamps
jdogresorg Sep 22, 2026
6f818fd
Merge: fix(health): report stale admission tips
jdogresorg Sep 22, 2026
fe4a172
fix(observability): bound unmatched-route metric label cardinality
jdogresorg Sep 22, 2026
bf247ac
Merge: fix(observability): bound unmatched-route metric label cardina…
jdogresorg Sep 22, 2026
56b74ff
build(ci): grade the fast tier on a push, defer the heavy tiers to th…
jdogresorg Sep 22, 2026
33094fe
fix(hub): update consensus_rules_digest comment for the armed testnet…
jdogresorg Sep 22, 2026
82074ad
fix(hub): record the deferred re-serve ruling at the finalized ring a…
jdogresorg Sep 22, 2026
38ea324
Merge: fix(hub): record the deferred re-serve ruling at the finalized…
jdogresorg Sep 22, 2026
e40c9e0
fix(hub): make consensus-identity reject a flag passed as a value, de…
jdogresorg Sep 23, 2026
574bf77
Harden strict sibling parity tests
jdogresorg Sep 23, 2026
30184db
test(hub): check every carrier twin instead of skipping on the first …
jdogresorg Sep 23, 2026
574faed
fix(hub): wire the XDEX round timeouts, strict oracle config parsing,…
jdogresorg Sep 23, 2026
4f2ac9d
fix(hub): register the token bridge gate in SHARED_GATES, report func…
jdogresorg Sep 23, 2026
b9a3418
test(hub): give the listener-ceiling source walk a cold-venue timeout
jdogresorg Sep 23, 2026
092b203
fix(hub): floor checkpoint bundle sections at CHECKPOINT_COMMITMENT, …
jdogresorg Sep 23, 2026
80fedb6
Merge: Harden strict sibling parity tests
jdogresorg Sep 23, 2026
22a4b1e
fix(oracle): ignore duplicate follower proposals
jdogresorg Sep 23, 2026
9fcfa19
Merge: fix(oracle): ignore duplicate follower proposals
jdogresorg Sep 23, 2026
08c756e
fix(hub): ship the validator fleet's rate-limit default
jdogresorg Sep 23, 2026
680deb4
Merge: fix(hub): ship the validator fleet's rate-limit default
jdogresorg Sep 23, 2026
3e0243f
perf(db): index the price_snapshots finalized frontier
jdogresorg Sep 23, 2026
a877578
fix(consensus): re-slide v0.20.1 BTC and DOGE activation heights, 40h…
jdogresorg Sep 23, 2026
4164134
Merge: perf(db): index the price_snapshots finalized frontier
jdogresorg Sep 23, 2026
e9368e8
chore(release): v0.20.1
jdogresorg Sep 23, 2026
3c58bf5
chore(consensus): remove internal rollout label from public comments
jdogresorg Sep 23, 2026
eeddcab
fix(hub): bound the health amplification path behind the public rate …
jdogresorg Sep 23, 2026
46e1496
fix(consensus): sign admit_block_btc on ATTEST batches only in the ad…
jdogresorg Sep 23, 2026
8321e66
fix(consensus): re-slide DOGE:testnet mirror admission to the 84h tra…
jdogresorg Sep 23, 2026
7caf094
Make the ATTEST batch-window claim exclusive under foundRows:true, an…
jdogresorg Sep 23, 2026
046429c
Merge branch 'cut/v0201-dq4' into cut/v0201-land
jdogresorg Sep 23, 2026
0fcc90a
Merge branch 'cut/batch-wire-gate' into cut/v0201-land
jdogresorg Sep 23, 2026
d984b46
Merge branch 'cut/hub-health-amplification' into cut/v0201-land
jdogresorg Sep 23, 2026
43432e0
Merge branch 'cut/hub-claim-purge-r2' into cut/v0201-land
jdogresorg Sep 23, 2026
34491ea
Pin the Node base image by digest to the consensus runtime
jdogresorg Sep 24, 2026
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
13 changes: 8 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ HUB_PORT=10000
# startup. ALWAYS set a strong random value in production. See
# CONFIGURATION.md "Silent-failure variables".
HUB_API_KEY=your-strong-api-key-here
# Optional: requests per minute per client (default 100). Does NOT cover the
# Optional: requests per minute per client without a hub key (default 100, on
# every role). A JSON-RPC batch costs one token per call. Does NOT cover the
# mirror-bootstrap routes, which carry their own budget below.
# HUB_RATE_LIMIT_RPM=100
# Optional: requests per minute per client presenting a configured hub key in
# x-api-key (default 60000): the fleet's own indexer push and replay traffic.
# HUB_AUTH_RATE_LIMIT_RPM=60000
# Optional: requests per minute per client on /hub-db/snapshot/* (default 600).
# A mirroring indexer bootstrapping from id 0 spends about 30 page reads per
# table set, in a burst, and it must not compete with ordinary polling for the
# budget above; that is what wedged the testnet mirrors on 2026-09-16.
# HUB_SNAPSHOT_RATE_LIMIT_RPM=600
# Optional: maximum JSON-RPC calls in one batch array (default 20). The limit
# above charges a whole batch ONE token while the router dispatches every
# element concurrently, so without this cap one request fans out into hundreds
# of handlers on the shared DB pool. Over the cap the hub answers 400 / -32600.
# Optional: maximum JSON-RPC calls in one batch array (default 20). The router
# dispatches every element concurrently, so without this cap one request fans
# out into hundreds of handlers on the shared DB pool. Over the cap the hub answers 400 / -32600.
# HUB_MAX_RPC_BATCH=20
# Optional: allowed CORS origin(s) (default disabled). `*` allows any origin;
# otherwise give one origin, or a comma-separated allowlist matched per-origin.
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.1] - 2026-09-23

### Fixed
- Re-slid BTC/DOGE testnet activations with LTC inert; canonical admission is non-throwing, and INERT legacy-signature mismatches stay on the invalid-row path.


### Changed
- The attestation batch publisher's `quarantinedWindows` statistic now counts only quarantines inside the current catch-up horizon. Dashboards keyed to the previous all-history count may see a one-time decrease after upgrade.

## [0.20.0] - 2026-09-17

### Changed
Expand Down
5 changes: 3 additions & 2 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ signatures.
| `HUB_API_KEY` | **Prod** | _empty_ | API key required on write requests and WS upgrades when set. **Empty means the hub refuses to boot unless `HUB_ALLOW_UNAUTHENTICATED=true` - see above.** |
| `HUB_ALLOW_UNAUTHENTICATED` | No | _unset_ | Declares that this hub knowingly runs with an unauthenticated write surface. Only consulted when `HUB_API_KEY` is empty. |
| `HUB_CONSENSUS_INPUT_ALERT_AFTER` | No | `3` | Consecutive consensus-input fetch failures before the hub alerts and `/health` reports degraded. |
| `HUB_RATE_LIMIT_RPM` | No | `100` | Requests per minute per client IP, on every route EXCEPT `/hub-db/snapshot/*` (see below). Over the limit the hub answers `429` with a JSON-RPC error body (code `-32029`) naming the limit, the window and the wait, alongside `Retry-After` and `RateLimit-*` headers. |
| `HUB_RATE_LIMIT_RPM` | No | `100` | The PUBLIC budget: requests per minute per client IP for callers without a hub key, on every route EXCEPT `/hub-db/snapshot/*` (see below), on every role including a validator hub. A JSON-RPC batch costs one token per call, not one per HTTP request. Over the limit the hub answers `429` with a JSON-RPC error body (code `-32029`) naming the limit, the window and the wait, alongside `Retry-After` and `RateLimit-*` headers. |
| `HUB_AUTH_RATE_LIMIT_RPM` | No | `60000` | The AUTHENTICATED budget: requests per minute per client IP for callers whose `x-api-key` matches `HUB_API_KEY`, `HUB_REORG_API_KEY` or `HUB_CONFIG_SECRETS_API_KEY`, metered in its own bucket so the fleet's indexer push and replay traffic keeps the fleet value without lifting the throttle for keyless callers. A wrong key is metered as public. Batches are charged per call here too. Never defaults below `HUB_RATE_LIMIT_RPM`; an unparseable or non-positive value keeps the default. A keyless hub has no authenticated tier. |
| `HUB_SNAPSHOT_RATE_LIMIT_RPM` | No | `600` | Requests per minute per client IP on the mirror-bootstrap routes `/hub-db/snapshot/*`, metered in their OWN bucket so a bootstrapping indexer and ordinary polling cannot starve each other. Sized to the measured drain: about 30 page reads for one mirror's full ten-table bootstrap, about 96 for three mirrors behind one address, retried every 30s while any mirror is still draining. Over the limit the hub answers `429` in the `{ "error": ... }` shape these REST routes already use. An unparseable or non-positive value keeps the default. |
| `HUB_RATE_LIMIT_EXEMPT_LOCAL` | No | `true` | Exempts loopback and private-range (RFC1918 / IPv6 ULA / link-local) callers from the per-IP limit, so a node's own indexer can rebuild price history from the chain at the shipped default instead of needing the limit raised by hand. Keyed on the post-`trust proxy` client IP, so a public caller behind a private-IP proxy is still limited. Set `false` to enforce the cap on every caller. |
| `HUB_MAX_RPC_BATCH` | No | `20` | Maximum JSON-RPC calls in one batch array. The router dispatches every element concurrently while the rate limit above charges the whole batch one token, so an uncapped batch amplifies one request into hundreds of DB-touching handlers. Over the cap the hub answers `400` with JSON-RPC error `-32600`. An unparseable or non-positive value keeps the default. |
| `HUB_MAX_RPC_BATCH` | No | `20` | Maximum JSON-RPC calls in one batch array. The router dispatches every element concurrently, so an uncapped batch amplifies one request into hundreds of DB-touching handlers; the rate limits above charge each element, and this caps the count outright. Over the cap the hub answers `400` with JSON-RPC error `-32600`. An unparseable or non-positive value keeps the default. |
| `CORS_ORIGIN` | No | `false` | Allowed CORS origin(s); unset disables cross-origin requests. `*` allows any origin, or give one origin or a comma-separated allowlist matched per-origin (browser wallet shells each send a different origin). A stray `*` inside a list is not a wildcard, so the grant fails closed. |

## Database (MariaDB)
Expand Down
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Pinned to node:22-bookworm, the tag .nvmrc and the sibling service images
# already declare. `node:latest` floats: a rebuild silently moves the runtime
# off the declared Node 22, so the image and the repo's pin drift apart with
# no signal anywhere.
FROM node:22-bookworm
# Pinned by digest to the node:22.23.2-bookworm build whose V8/ICU match
# xchain-vm's consensus runtime pin: the floating node:22-bookworm tag moved
# to a Node patch that fails that check, so a tag alone can silently drift
# the image off the runtime the fleet requires.
FROM node:22.23.2-bookworm@sha256:dd5847a04b0deee391fa145f1f4c6d214196668b6bcc7988ebed67249f226844

RUN mkdir /XChainHub/
COPY ./package.json /XChainHub/package.json
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# XChain Platform Hub

<p align="center">
<img src="https://img.shields.io/badge/version-0.20.0-blue" alt="Version">
<img src="https://img.shields.io/badge/version-0.20.1-blue" alt="Version">
<img src="https://img.shields.io/badge/tests-7%2C342%2B%20passing-brightgreen" alt="Tests">
<img src="https://img.shields.io/badge/node-%3E%3D22-green" alt="Node">
<img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-blue" alt="License">
Expand Down
48 changes: 45 additions & 3 deletions bin/ci-full.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,36 @@ SELF="$(pwd)"
SIB="$(cd .. && pwd)"

FAILED=""
# >>> ci-tier (generated block; re-run the tier wirer to update) >>>
# Tier classes. A push grades the FAST tier only: the unit job, the pin and
# drift guards, and the structure and hygiene checks the hook runs before it
# dispatches. The tiers named below (coverage re-runs, perf scenarios) are
# skipped when the gate sets CI_TIER=fast, and each skip is recorded so the
# closing verdict can never claim a green it did not earn. Nothing stops
# being graded: a scheduled sweep re-runs this same script with CI_TIER=full
# on every repo every three hours and before any release or deploy, and a
# red there is tracked down and fixed first. CI_TIER is unset for a hand
# run, so a bare `npm run ci:full` still runs every tier as it always did.
CI_TIER_FULL_ONLY=(
"perf (test:perf)"
"coverage ratchet (coverage:check)"
)
DEFERRED=""
ci_tier_deferred() {
[ "${CI_TIER:-full}" = "fast" ] || return 1
local t
for t in ${CI_TIER_FULL_ONLY[@]+"${CI_TIER_FULL_ONLY[@]}"}; do
if [ "$t" = "$1" ]; then
DEFERRED="$DEFERRED [$1]"
echo; echo "ci:full ===== $1 DEFERRED (CI_TIER=fast, runs in the full sweep) ====="
return 0
fi
done
return 1
}
# <<< ci-tier <<<
run_tier() {
ci_tier_deferred "$1" && return 0 # ci-tier guard (generated)
local name="$1"; shift
echo; echo "ci:full ===== $name ====="
if "$@"; then
Expand Down Expand Up @@ -122,7 +151,7 @@ run_tier "measurement tools (bin/test)" \
npx mocha 'bin/test/**/*.test.js' --no-config --timeout 120000 --recursive --exit

# --- job: ci (XChain-Platform/.github ci-reusable.yml -> npm run ci) -------
run_tier "ci" npm run ci
run_tier "ci" env XCHAIN_REQUIRE_SIBLINGS=1 npm run ci

# --- job: perf -------------------------------------------------------------
# The workflow gives this job its own MariaDB service container; here the DB is
Expand Down Expand Up @@ -159,11 +188,24 @@ run_tier "drift: vendored observability shim vs canonical (six consumers)" \
npm run check:observability-sync

# --- job: coverage ---------------------------------------------------------
run_tier "coverage ratchet (coverage:check)" npm run coverage:check
run_tier "coverage ratchet (coverage:check)" \
env XCHAIN_REQUIRE_SIBLINGS=1 npm run coverage:check

echo
# >>> ci-tier summary (generated) >>>
echo "ci:full: tier class ${CI_TIER:-full}"
if [ -n "${DEFERRED:-}" ]; then
echo "ci:full: DEFERRED to the full sweep:$DEFERRED"
fi
# <<< ci-tier summary <<<
if [ -n "$FAILED" ]; then
echo "ci:full: RED tiers:$FAILED"
exit 1
fi
echo "ci:full: all tiers green (same set GitHub CI runs)"
# >>> ci-tier verdict (generated) >>>
if [ "${CI_TIER:-full}" = "fast" ]; then
echo "ci:full: all FAST tiers green; the DEFERRED tiers above were NOT graded here"
else
echo "ci:full: all tiers green (same set GitHub CI runs)"
fi
# <<< ci-tier verdict <<<
Loading
Loading