Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3914f0e
ci(e2e): boot the bitcoin gas rail beside the litecoin and dogecoin m…
jdogresorg Sep 16, 2026
111f833
ci(e2e): create config/ before writing the two-stack port files
jdogresorg Sep 16, 2026
0bbfc8a
ci(e2e): raise the runner MariaDB connection cap for the two-stack legs
jdogresorg Sep 16, 2026
124dc1f
ci(e2e): run the litecoin and dogecoin legs in validator mode with a …
jdogresorg Sep 16, 2026
fa0614b
fix(config): hand the e2e-test container the hub API key on a keyed host
jdogresorg Sep 16, 2026
0697233
ci(e2e): hand the validator-mode hub its indexer URLs before the stac…
jdogresorg Sep 16, 2026
2141385
fix(hub): pass XCHAIN_CONFIRMATIONS_BTC/LTC/DOGE through to the hub, …
jdogresorg Sep 16, 2026
2d4b33b
chore: level develop with master after the v0.19.0 cut
jdogresorg Sep 16, 2026
e79af6e
fix(nightly): route the coin indexer to the bitcoin indexer for the b…
jdogresorg Sep 16, 2026
c553c75
docs(changelog): move the confirmations passthrough line back under U…
jdogresorg Sep 16, 2026
db472dc
fix(bootstrap): resolve the signing pubkey from src/config after the …
jdogresorg Sep 16, 2026
5df3790
Merge remote-tracking branch 'origin/master' into level-develop-maste…
jdogresorg Sep 16, 2026
4fec630
fix(migration-precondition): add the v0.19.0+ db/migration CLI path
jdogresorg Sep 17, 2026
4df75e0
fix(bootstrap-service-test): point the healthy-listing probe at a fre…
jdogresorg Sep 17, 2026
a4ff20a
fix(ci): roster-driven need_sib and honest sibling guards in coverage
jdogresorg Sep 17, 2026
b998121
feat(config): pass XC_MIRROR_ADMISSION_ACTIVATION through to regtest …
jdogresorg Sep 17, 2026
d84b7a2
chore(release): bump to 0.20.0, refresh the README badge and record t…
jdogresorg Sep 17, 2026
81ccd7e
release: regenerate the v0.20.0 manifest from the signed sibling tags
jdogresorg Sep 18, 2026
6195e10
Merge pull request #48 from XChain-Platform/manifest/v0200
jdogresorg Sep 19, 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
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,36 @@ jobs:
steps:
- uses: actions/checkout@v4

# Re-runs the same suite the `ci` job runs, so it needs the same
# sibling checkout that job's shared workflow does elsewhere.
# Mirrored verbatim (same roster, same clone) so they cannot drift.
- name: Check out declared sibling repositories
id: siblings
env:
SIBLINGS_REF: ${{ github.ref == 'refs/heads/master' && 'master' || 'develop' }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
if [ ! -f .ci-siblings ]; then
echo "no .ci-siblings in this repo; single-repo layout"
exit 0
fi
repos=$(sed 's/#.*//' .ci-siblings | tr -d '\r' | awk 'NF')
cd "$GITHUB_WORKSPACE/.."
for repo in $repos; do
rm -rf "$repo"
url="https://github.com/$OWNER/$repo.git"
git clone --quiet --depth 1 --branch "$SIBLINGS_REF" "$url" "$repo" 2>/dev/null \
|| git clone --quiet --depth 1 "$url" "$repo"
echo "sibling $repo @ $(git -C "$repo" rev-parse --abbrev-ref HEAD) $(git -C "$repo" rev-parse --short HEAD)"
if [ -f "$repo/package.json" ]; then
( cd "$repo" && npm ci --omit=dev --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 ) \
|| ( cd "$repo" && npm install --omit=dev --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 ) \
|| echo "sibling $repo: dependency install failed, guards needing its modules will say so"
fi
done
echo "checked-out=1" >> "$GITHUB_OUTPUT"

- name: Use Node.js 22
uses: actions/setup-node@v4
with:
Expand All @@ -104,7 +134,12 @@ jobs:
# pool, so the fetch 403s, installModules throws a ReleaseIntegrityError
# before the bootstrap summary is ever reported, and the failure surfaces as
# an unrelated "expected false to be true" on a stub assertion.
#
# Armed only when the step above actually checked siblings out,
# so a repo with none still reports real coverage instead of
# failing on an environment gap coverage:check did not create.
- name: Coverage thresholds (c8 --check-coverage)
env:
XCHAIN_REQUIRE_SIBLINGS: ${{ steps.siblings.outputs.checked-out == '1' && '1' || '' }}
GITHUB_TOKEN: ${{ github.token }}
run: npm run coverage:check
29 changes: 26 additions & 3 deletions .github/workflows/nightly-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,23 @@ jobs:
# the hub joins every stack's docker network, so the in-network container
# names on the indexer's own port 3004 are reachable once the stacks are
# up; the same shape a regtest rail's .env gives its hub.
# The engine then proposes a source leg only at the hub's per-coin
# confirmation depth, six blocks on BTC by default, and nothing on this
# runner mines six BTC blocks behind a lock: run 35124072478 reached
# "CrossChainBridge: not proposing BTC:3 (below depth 6)" and the suite
# gave up on the credit 120 s later. The depths are pinned to 1 the way a
# regtest rail pins them; the hub clamps them back up to the default off
# regtest, so the same three names are inert on any real network.
if [ "$COIN" != "bitcoin" ]; then
echo "XDEX_SEED_LOCAL_VALIDATOR=1" >> "$GITHUB_ENV"
code=$(case "$COIN" in litecoin) echo LTC ;; dogecoin) echo DOGE ;; esac)
{
echo "BTC_INDEXER_API_URL=http://xchain-node-bitcoin-regtest-xchain-indexer:3004"
echo "BTC_INDEXER_URL=http://xchain-node-bitcoin-regtest-xchain-indexer:3004"
echo "${code}_INDEXER_URL=http://xchain-node-$COIN-regtest-xchain-indexer:3004"
echo "XCHAIN_CONFIRMATIONS_BTC=1"
echo "XCHAIN_CONFIRMATIONS_LTC=1"
echo "XCHAIN_CONFIRMATIONS_DOGE=1"
} >> "$GITHUB_ENV"
fi
# Prints the pubkey and never the seed (validator status reads the
Expand Down Expand Up @@ -422,9 +432,20 @@ jobs:
# already reach the runner's MariaDB on. Every key in the coin's config
# file is injected into that coin's containers, the e2e runner included,
# which is how this one variable reaches the suite.
# 3. A route from the coin's INDEXER to the bitcoin indexer. Before it
# credits a bridged transfer the destination indexer fetches the escrow
# proof from the origin chain's indexer, resolved from its own env as
# BTC_INDEXER_API_URL (xchain-indexer bridge_proof_client transport,
# the hub's three-tier idiom); with nothing wired it holds the block at
# the bridge proof barrier instead of guessing. Run 35140173657 (the
# first leg past the depth gate) sat there for the 900 s hold ceiling,
# "no origin-chain indexer endpoint is configured for the escrow proof",
# while the chain ran 143 blocks ahead. The install attaches the bitcoin
# indexer to the coin's docker network, so its container name on the
# indexer's own port serves, the same value the hub is handed above.
#
# Only ports and that host go in the files: credentials are generated into the
# untracked .local sidecars by the install itself, exactly as before.
# Only ports and those two routes go in the files: credentials are generated
# into the untracked .local sidecars by the install itself, exactly as before.
- name: Publish distinct host ports for the two-stack legs
if: env.COIN != 'bitcoin'
run: |
Expand All @@ -450,7 +471,9 @@ jobs:
# Every file under config/ is gitignored, so a fresh checkout has no
# such directory at all; ConfigService reads it by path, never creates it.
mkdir -p config
write_ports "config/$COIN-regtest" "$BASE" "BTC_SERVICE_HOST=$XCHAIN_NODE_EXTERNAL_DB_HOST"
write_ports "config/$COIN-regtest" "$BASE" \
"BTC_SERVICE_HOST=$XCHAIN_NODE_EXTERNAL_DB_HOST" \
"BTC_INDEXER_API_URL=http://xchain-node-bitcoin-regtest-xchain-indexer:3004"
write_ports config/bitcoin-regtest 3020

- name: Boot the regtest stack (clones every service at ${{ github.event.inputs.ref || 'develop' }})
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.0] - 2026-09-17

### Added
- `XC_MIRROR_ADMISSION_ACTIVATION` passes through to the hub and regtest indexer containers.

### Changed
- Nightly Litecoin and Dogecoin jobs provision validator configuration, API access, database headroom, and a Bitcoin gas rail.

### Fixed
- Confirmation overrides pass through to the hub for nightly bridged-gas finalization.
- Litecoin and Dogecoin indexers receive the Bitcoin indexer URL needed for bridge escrow proofs.
- Bootstrap restores resolve the signing public key from its relocated configuration path.
- Migration preflight accepts the v0.19.0 and later `db/migration` CLI layout.
- Coverage checks derive sibling requirements from the repository roster.

## [0.19.1] - 2026-09-16

### Fixed
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 Node

<p align="center">
<img src="https://img.shields.io/badge/version-0.19.1-blue" alt="Version">
<img src="https://img.shields.io/badge/version-0.20.0-blue" alt="Version">
<img src="https://img.shields.io/badge/tests-3%2C387%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
23 changes: 21 additions & 2 deletions bin/ci-full.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,29 @@ need_sib() {
done
}

need_sib xchain-hub
# Read the roster ci-reusable.yml's own sibling-checkout step reads
# (.ci-siblings) with the same parse, instead of a second hard-coded
# list the two could drift behind.
CI_SIBLINGS_FILE="$SELF/.ci-siblings"
DECLARED_SIBLINGS=()
if [ -f "$CI_SIBLINGS_FILE" ]; then
while IFS= read -r s; do
DECLARED_SIBLINGS+=("$s")
done < <(sed 's/#.*//' "$CI_SIBLINGS_FILE" | tr -d '\r' | awk 'NF')
fi
if [ "${#DECLARED_SIBLINGS[@]}" -gt 0 ]; then
need_sib "${DECLARED_SIBLINGS[@]}"
fi

# --- job: ci (XChain-Platform/.github ci-reusable.yml -> npm run ci) -------
run_tier "ci" npm run ci
# ci-reusable.yml arms XCHAIN_REQUIRE_SIBLINGS whenever it checked
# siblings out, so every sibling guard fails loud on a miss instead of
# skipping; match that here for a true local twin.
if [ "${#DECLARED_SIBLINGS[@]}" -gt 0 ]; then
run_tier "ci" env XCHAIN_REQUIRE_SIBLINGS=1 npm run ci
else
run_tier "ci" npm run ci
fi

# --- job: drift-guards -------------------------------------------------------
# Run FROM the parent so sync-coins.sh sees the canonical + vendored pair the
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xchain-node",
"version": "0.19.1",
"version": "0.20.0",
"description": "xchain-node allows users to install, configure and run XChain platform nodes.",
"license": "AGPL-3.0-or-later",
"repository": {
Expand Down
8 changes: 6 additions & 2 deletions src/config/env_views.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ const LIST_VIEWS = {
"ANCHOR_ELECTION_TOLERANCE_BLOCKS", "ANCHOR_REWARD_PER_PUBLISH",
"ANCHOR_CHECKPOINT_EVERY_N", "DOGE_ENCODER_URL", "DOGE_ENCODER_API_KEY", "DOGE_ADDRESS",
"DOGE_PUBKEY_HEX", "DOGE_LOW_BALANCE_THRESHOLD", "XDEX_SEED_LOCAL_VALIDATOR",
"XDEX_SNAPSHOT_BLOCK", "HUB_TRUST_PROXY", "HUB_NETWORK", "ORACLE_MIN_SUBMISSIONS",
"XDEX_SNAPSHOT_BLOCK",
"XCHAIN_CONFIRMATIONS_BTC", "XCHAIN_CONFIRMATIONS_LTC", "XCHAIN_CONFIRMATIONS_DOGE",
"HUB_TRUST_PROXY", "HUB_NETWORK", "ORACLE_MIN_SUBMISSIONS",
"ORACLE_ROUND_INTERVAL", "ORACLE_SUBMISSION_WINDOW", "ORACLE_BATCH_WINDOW_ROUNDS",
"ORACLE_BATCH_GRACE_MS", "ORACLE_BATCH_SIGN_TIMEOUT_MS",
"ORACLE_BATCH_BUFFER_MAX_ROUNDS", "ORACLE_BATCH_LANDING_RESERVE_MS",
Expand All @@ -57,6 +59,7 @@ const LIST_VIEWS = {
"XCHAIN_PRICE_CONFIRMATION_BUFFER", "XCHAIN_PRICE_BOOTSTRAP_SATS",
"XCHAIN_PRICE_MIN_BTC_VOLUME", "HUB_ALLOW_UNAUTHENTICATED",
"XC_ROLLCALL_REGTEST_ACTIVATION", "XC_ROLLCALL_GATES_REGTEST_ACTIVATION",
"XC_MIRROR_ADMISSION_ACTIVATION",
"XCHAIN_PRICE_INDEXER_DB_SECRET"
]],
// The indexer's regtest genesis bootstrap (ConfigService genesisPassthroughVars).
Expand All @@ -71,7 +74,8 @@ const LIST_VIEWS = {
INDEXER_ROLLCALL_ENV: ['indexer rollcall', [
"DOGE_INDEXER_API_URL", "DOGE_INDEXER_API_KEY", "XC_ROLLCALL_REGTEST_ACTIVATION",
"XC_ROLLCALL_GATES_REGTEST_ACTIVATION", "HUB_SYNC_ANCHOR_ATTEST_GRACE_S",
"HUB_PRICE_SYNC_TIMEOUT_MS", "XCHAIN_COINPAY_EXPIRATION_S"
"HUB_PRICE_SYNC_TIMEOUT_MS", "XCHAIN_COINPAY_EXPIRATION_S",
"XC_MIRROR_ADMISSION_ACTIVATION"
]],
// The hub-sync watermark graces a regtest indexer is handed (hubSyncRegtestGraceVars).
HUB_SYNC_GRACE_ENV: ['hub sync grace', [
Expand Down
60 changes: 30 additions & 30 deletions src/release-manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"_comment": [
"Pinned component set for XChain Platform v0.19.1.",
"Pinned component set for XChain Platform v0.20.0.",
"Generated by bin/write-release-manifest.js from the ACTUAL tagged master merge commits.",
"xchain-node is the carrier and is not listed: checking out its tag IS this manifest.",
"A train tags only the repos it touches. This one moves xchain-sync.",
"A train tags only the repos it touches. This one moves xchain-vm, xchain-decoder, xchain-indexer, xchain-hub, xchain-sync, xchain-encoder, xchain-utxo-tracker, xchain-explorer, xchain-sdk, xchain-e2e-test and xchain-regtest-miner.",
"Every other component is unchanged and keeps the tag it already carries, which is",
"what section 4 means by a version being the platform version at which a component",
"last changed. A gap is unchanged, not skipped.",
Expand All @@ -12,70 +12,70 @@
"repo's origin/master, and is a GPG-signed annotated tag verifying against the",
"platform release key (fingerprint 1DA7C4896F56EA22CF491EDF4361611A82F90B70)."
],
"platform_version": "0.19.1",
"released": "2026-09-16",
"platform_version": "0.20.0",
"released": "2026-09-18",
"components": {
"xchain-vm": {
"tag": "v0.19.0",
"commit": "45d2fd23e20b2d77df796615a50342310915abd2"
"tag": "v0.20.0",
"commit": "c28d3338318ba0494eebe4203432dfe78b127f2a"
},
"xchain-decoder": {
"tag": "v0.19.0",
"commit": "bb758689212da96a03369e7e6fd4ebc72f4dc4c9"
"tag": "v0.20.0",
"commit": "0b07c59464e3631db5d0d775f5aa9c550bbe7b0c"
},
"xchain-indexer": {
"tag": "v0.19.0",
"commit": "715b2fee2032cc20f1eb04227d1bf6ef8cac88b5"
"tag": "v0.20.0",
"commit": "777ef8604b400f398a05bdb5b0da4e2242896b98"
},
"xchain-hub": {
"tag": "v0.19.0",
"commit": "16ef8adc97e88c24ebc865aa4bfa248deeb4d45a"
"tag": "v0.20.0",
"commit": "0f7c78ac6fb9c139155d8df018e9668e70ca63f3"
},
"xchain-sync": {
"tag": "v0.19.1",
"commit": "5b44cb558b3e595ef46a2403a9536ec5183fd2e7"
"tag": "v0.20.0",
"commit": "5c03236b2b77d6b49b5282cc1b4d6ebbe1913efe"
},
"xchain-encoder": {
"tag": "v0.19.0",
"commit": "8616f3d09ca54013bec6ff70ac851a6ba10b035a"
"tag": "v0.20.0",
"commit": "830d937c0c2cbbbe4d5cb6ce2b11d308e30a16ca"
},
"xchain-utxo-tracker": {
"tag": "v0.19.0",
"commit": "7e3223e6caeefb10f9d918c19c73856dbe559a9e"
"tag": "v0.20.0",
"commit": "a14eea102792338ee6d02f3afeea4719981936f4"
},
"xchain-explorer": {
"tag": "v0.19.0",
"commit": "dd79d7fb584d532df2b366a22704fd3442bac93a"
"tag": "v0.20.0",
"commit": "6daa89b0ef67315d52328b8b1bc4416139f8c3d5"
},
"xchain-sdk": {
"tag": "v0.19.0",
"commit": "d488b0d2ad95c3021b20e439efedec7cc47cf0e7"
"tag": "v0.20.0",
"commit": "340ff29550d8b24f2360b0eec97eb3c5a64b3701"
},
"xchain-e2e-test": {
"tag": "v0.19.0",
"commit": "1f9486c74aa9c3b68b440250c0c1000006cacbe9"
"tag": "v0.20.0",
"commit": "469bd3e652ae9c715ac58d4a28bb9e19cf3abd8d"
},
"xchain-contracts": {
"tag": "v0.17.0",
"commit": "684a6311be7232542346c984bd9f2d62f3251810"
},
"xchain-regtest-miner": {
"tag": "v0.19.0",
"commit": "1f447321bce3386635d080285a337d7ed03b191d"
"tag": "v0.20.0",
"commit": "9426829225b19844f2d5de434dd2bed3605fcb20"
}
},
"trainActivation": {
"ruleSetVersion": "0.19.0",
"ruleSetVersion": "0.20.0",
"classification": "major",
"heights": {
"mainnet": 9999999999,
"regtest": 0,
"testnet": 152787
"testnet": 153116
},
"computedFromBtcTip": {
"mainnet": 967299,
"mainnet": 967625,
"regtest": 0,
"testnet": 152716
"testnet": 153018
}
}
}
24 changes: 22 additions & 2 deletions src/services/config_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,11 +772,17 @@ async function getDefaultConfig(module, coin, network) {
// XC_ROLLCALL_GATES_REGTEST_ACTIVATION follows XC_ROLLCALL_REGTEST_ACTIVATION's
// same env-derived regtest shape (D84): it arms ROLLCALL v1 and the rules-aware
// attestation set separately from the rail, so a venue can drive v0 as its control.
//
// XC_MIRROR_ADMISSION_ACTIVATION rides the same regtest-only shape: without a
// path here the indexer side of the admission-map mirror can never be armed on
// regtest (row 24x), and it must arm together with the hub's copy above or the
// admission-era canonical refuses a legacy-map row and halts the block loop.
if (network === Network.REGTEST) rollcallPassthroughVars.push("XC_ROLLCALL_REGTEST_ACTIVATION",
"XC_ROLLCALL_GATES_REGTEST_ACTIVATION",
"HUB_SYNC_ANCHOR_ATTEST_GRACE_S",
"HUB_PRICE_SYNC_TIMEOUT_MS",
"XCHAIN_COINPAY_EXPIRATION_S")
"XCHAIN_COINPAY_EXPIRATION_S",
"XC_MIRROR_ADMISSION_ACTIVATION")
for (const varName of rollcallPassthroughVars) {
if (config.INDEXER_ROLLCALL_ENV[varName] !== undefined && config.INDEXER_ROLLCALL_ENV[varName] !== "") {
defaultValues[varName] = config.INDEXER_ROLLCALL_ENV[varName]
Expand Down Expand Up @@ -1148,6 +1154,15 @@ async function getDefaultConfig(module, coin, network) {
"DOGE_ENCODER_URL", "DOGE_ENCODER_API_KEY", "DOGE_ADDRESS",
"DOGE_PUBKEY_HEX", "DOGE_LOW_BALANCE_THRESHOLD",
"XDEX_SEED_LOCAL_VALIDATOR", "XDEX_SNAPSHOT_BLOCK",
// Per-coin confirmation depth the hub's cross-chain engines wait for
// before proposing a source leg (coins/index.js resolveConfirmations).
// A regtest venue pins these to 1 so a bridge lock finalizes on the
// next block instead of six BTC blocks nothing is mining (the nightly
// two-stack legs sat on "not proposing BTC:3 (below depth 6)" until
// the 120 s credit wait gave up). Inert on mainnet and testnet: the
// hub clamps a value below the per-coin default UP to that default
// off regtest, so this can only raise the depth on a real network.
"XCHAIN_CONFIRMATIONS_BTC", "XCHAIN_CONFIRMATIONS_LTC", "XCHAIN_CONFIRMATIONS_DOGE",
// Reverse-proxy trust for the hub's express API (rate-limiter IP
// keying). Default 'loopback' suits the Apache-on-same-host prod
// topology; containerized hubs see the docker bridge as the peer,
Expand Down Expand Up @@ -1281,7 +1296,12 @@ async function getDefaultConfig(module, coin, network) {
// no-network-gate reasoning: it arms ROLLCALL v1 and the rules-aware
// attestation set separately from the rail, so a venue can drive v0 as its
// control, and the hub's own rollcall_gates_activation.js gates it for real.
"XC_ROLLCALL_GATES_REGTEST_ACTIVATION"
"XC_ROLLCALL_GATES_REGTEST_ACTIVATION",
// XC_MIRROR_ADMISSION_ACTIVATION follows the same no-network-gate shape
// (D84 precedent): it arms the admission-map mirror and its consumer and
// barrier gates together, so a venue arms as a unit; the hub's own
// mirror-admission gate module gates it for real.
"XC_MIRROR_ADMISSION_ACTIVATION"
]
for (const varName of hubPassthroughVars) {
// Secret-bearing names in this list (XCHAIN_PRICE_INDEXER_DB_PASS) are also
Expand Down
Loading
Loading