Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3db1372
Merge master (v0.26.1 release) back into develop
jdogresorg Oct 21, 2026
7344a8b
Merge master (v0.26.2 release) back into develop
jdogresorg Oct 22, 2026
850fc0f
Record the v0.23.3 maintenance release in the changelog
jdogresorg Oct 23, 2026
ddd8d66
Defer regtest validator seeding until stack readiness
jdogresorg Oct 23, 2026
eec8cbf
sync trunk
jdogresorg Oct 23, 2026
84f201e
Split nightly workflow contract checks
jdogresorg Oct 23, 2026
09f5fd5
Merge: Split nightly workflow contract checks
jdogresorg Oct 23, 2026
f955b59
Merge master (v0.26.3 release) back into develop
jdogresorg Oct 23, 2026
c933f6e
Wait for a recreated hub to open its API before giving up the config …
jdogresorg Oct 24, 2026
3d78754
Override zod to the patched release and refresh body-parser
jdogresorg Oct 24, 2026
a87e151
Honour a forced bootstrap on an installed module, wait out a cold Mar…
jdogresorg Oct 24, 2026
33ea04d
Persist automatically moved service ports
jdogresorg Oct 24, 2026
1092916
Merge: Persist automatically moved service ports
jdogresorg Oct 24, 2026
ff8515c
Move the workflow Node pin to 22.23.4
jdogresorg Oct 25, 2026
6521e25
Add coordinated hub checkpoint reset
jdogresorg Oct 25, 2026
c01a625
Keep checkpoint reset within structure boundaries
jdogresorg Oct 25, 2026
974cb41
Merge: Keep checkpoint reset within structure boundaries
jdogresorg Oct 25, 2026
13b923d
Let reset run its database steps on a named MariaDB container without…
jdogresorg Oct 26, 2026
2dc1701
Let a named-container reset take its root from XCHAIN_NODE_DB_CONTAIN…
jdogresorg Oct 26, 2026
6579f14
Let a named-container reset run as the stack's own database users whe…
jdogresorg Oct 26, 2026
bd3b91f
Read service database logins from container config so a stopped stack…
jdogresorg Oct 26, 2026
8bee1f5
Keep bootstrap capture logs on work volume
jdogresorg Oct 26, 2026
c551b5c
Merge: Keep bootstrap capture logs on work volume
jdogresorg Oct 26, 2026
dcae229
fix(command-lock): publish the lock file complete so a live creator's…
jdogresorg Oct 30, 2026
d45c2ec
fix(cli): name an unknown chain or network in clear-reorg-halt
jdogresorg Oct 30, 2026
83b0402
fix(reset): report a failed database step and name the services left …
jdogresorg Oct 30, 2026
28f1362
fix(autoheal): stamp a restart when it finishes, not at pass start
jdogresorg Oct 30, 2026
8fa8bdf
fix(cli): reject an unknown chain or network before building the serv…
jdogresorg Oct 30, 2026
9f16136
fix(hub-push): cap the hub config push at three minutes of wall time
jdogresorg Oct 30, 2026
f3bf037
fix(hub-push): treat a hub answer carrying an error as a failed confi…
jdogresorg Oct 30, 2026
b9a387d
fix(precheck): keep read-only commands from installing or restarting …
jdogresorg Oct 30, 2026
20ab027
fix(exec): exit non-zero when the in-container command fails or match…
jdogresorg Oct 30, 2026
8c3fe10
Fail lifecycle commands that match nothing and keep the explorer out …
jdogresorg Oct 30, 2026
7ce159c
Note the lifecycle exit and explorer scope fixes in the changelog
jdogresorg Oct 30, 2026
87494ed
Expect named refusals in the regression, fuzz and e2e suites
jdogresorg Oct 30, 2026
c0dd550
fix: land verified review-round fixes
jdogresorg Oct 30, 2026
27e7f9b
Merge develop into release/v0.27.0
jdogresorg Oct 31, 2026
65bead2
fix: land verified review-round fixes
jdogresorg Oct 31, 2026
e701585
Merge develop into release/v0.27.0
jdogresorg Oct 31, 2026
bcb9b14
fix: land verified review-round fixes
jdogresorg Oct 31, 2026
6cf273f
Merge develop into release/v0.27.0
jdogresorg Oct 31, 2026
ed0e4c9
fix: land verified review-round fixes
jdogresorg Oct 31, 2026
7cea7af
fix: land verified review-round fixes
jdogresorg Oct 31, 2026
7c5860d
Merge develop into release/v0.27.0
jdogresorg Nov 1, 2026
b5f283b
Release v0.27.0
jdogresorg Nov 1, 2026
e44296f
Patch body-parser to 2.4.0 and follow-redirects to 1.16.2 for two hig…
jdogresorg Nov 1, 2026
01f07e4
Release v0.27.0 manifest
jdogresorg Nov 1, 2026
b86bd21
Merge pull request #87 from XChain-Platform/release-manifest-v0.27.0
jdogresorg Nov 1, 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
2 changes: 1 addition & 1 deletion .github/workflows/attest-mirror-leg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
# on some images, v22.23.3 on others, the latter failing the consensus pin).
- uses: actions/setup-node@v4
with:
node-version: '22.22.3'
node-version: '22.23.4'

- name: Start headless MariaDB (for the image-building install)
run: |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"
cache: npm

- name: Install dependencies
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@fd9160695d677f0538e959460ba5d8dfbb7f3c8e # pin: XChain-Platform/.github @ master 2026-08-31; bump deliberately
with:
# The fleet's consensus runtime; a floating "22" now resolves 22.23.3, which the VM runtime gate refuses.
node-version: "22.22.3"
node-version: "22.23.4"

# Cross-repo drift guards: this repo vendors the canonical
# coin registry from xchain-hub, and per-repo CI cannot see sibling repos, so
Expand All @@ -93,7 +93,7 @@ jobs:
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"

- name: Coin-registry byte-identity vs canonical
run: xchain-hub/bin/sync-coins.sh --check --only xchain-node
Expand Down Expand Up @@ -141,7 +141,7 @@ jobs:
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"
cache: npm

- name: Install dependencies
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/nightly-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"

# WHY THE COIN LIST IS EVENT-DEPENDENT. A scheduled run carries NO inputs, so
# without this it would silently test bitcoin alone and the nightly would be
Expand Down Expand Up @@ -361,7 +361,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"

- name: Reclaim runner disk (coin daemon + per-service images are large)
run: |
Expand Down Expand Up @@ -463,12 +463,6 @@ jobs:
echo "HUB_NETWORK=regtest"
echo "ORACLE_MIN_SUBMISSIONS=1"
} >> "$GITHUB_ENV"
# An identity alone does not finalize a bridge transfer: the engine signs
# against the cross_chain capability set, which is EMPTY on a fresh regtest
# until something STAKEs, and an empty set fails closed. This host-env
# passthrough (ConfigService hubPassthroughVars) makes the hub seed itself
# as the single validator on an empty set; the hub honours it on regtest
# only. Non-bitcoin legs only, so the bitcoin opt-in keeps its regime.
# The hub's cross-chain engines resolve each chain's indexer URL ONCE, at
# engine start, from env or from the configs table, and skip a chain with
# none on every later tick. On this runner the hub boots before either
Expand All @@ -487,7 +481,6 @@ jobs:
# 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"
Expand Down Expand Up @@ -594,6 +587,13 @@ jobs:
node src/index.js install "$STACK_REF" all bitcoin regtest
fi

- name: Enable the regtest validator seed after both stacks are ready
if: env.COIN != 'bitcoin'
run: |
export XDEX_SEED_LOCAL_VALIDATOR=1
echo "XDEX_SEED_LOCAL_VALIDATOR=1" >> "$GITHUB_ENV"
node src/index.js recreate xchain-hub

- name: Select this shard's action suites
# The plan job owns the split; this only reads this shard's entry, so the
# pattern run here is the one the aggregate later checks it against.
Expand Down Expand Up @@ -777,7 +777,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: "22.22.3"
node-version: "22.23.4"

# A shard that never uploaded simply has no artifact here; the merge names
# it as missing, so a failed download is not allowed to stop the job first.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rail-leg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ jobs:
# on some images, v22.23.3 on others, the latter failing the consensus pin).
- uses: actions/setup-node@v4
with:
node-version: '22.22.3'
node-version: '22.23.4'

- name: Resolve the anchor arm height
id: anchor-arms
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.27.0] - 2026-11-01

### Added
- Added coordinated hub checkpoint reset, and reset can now run on a named MariaDB container without the hub push.

### Changed
- Reset can use a root password variable or the stack's own database logins, and moved service ports are now persisted.

### Fixed
- Lifecycle commands now exit non-zero when the in-container command fails or matches nothing, and read-only commands no longer install or restart the hub.
- A hub answer carrying an error now counts as a failed config push, and the push is capped at three minutes.
- A failed reset database step is reported with the services left down, and the command lock file is published whole.
- Autoheal now restarts only running containers, backs off repeated restarts, and keeps going when a container vanishes mid-pass.
- The bootstrap health gate now ignores halts an operator has cleared and counts a sync halt held only in sync state.
- Module, explorer and config sidecar handling and the command lock were corrected, and status reports more accurately.

### Fixed
- Kept bootstrap publisher capture logs on its configured temporary work volume.
- Published the command lock file complete so a contender never reclaims a live creator's empty lock.
- Made clear-reorg-halt name an unknown chain or network instead of reporting no decoder container installed.
- Made a reset whose database step fails after the stop report the failure and name the services it left down.
- Stamped each autoheal restart when it finishes so the cooldown and recovery probation no longer start early.
- Made a stop, start or other service command reject an unknown chain or network instead of acting on the shared explorer.
- Capped the hub config push at three minutes of wall time so a hung hub no longer holds the command lock for an hour.
- Made a hub config push that the hub answers with an error count as failed instead of delivered.
- Stopped the read-only ps, tail, logs and monitor commands from installing the hub and restarting one the operator stopped.
- Made exec exit non-zero with a short reason when the in-container command fails or matches no installed service.
- Made start, stop and restart exit non-zero when they match no installed service.
- Kept the shared explorer out of a start, stop, restart or uninstall scoped to one chain.

## [0.26.3] - 2026-10-23

### Changed
Expand Down Expand Up @@ -64,6 +94,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Started the bundled MariaDB with a 1G InnoDB buffer pool by default, settable with `XCHAIN_NODE_DB_BUFFER_POOL_SIZE`.

## [0.23.3] - 2026-10-22

### Changed
- Pinned the v0.23.3 base-image and Brotli-guard patch train in the release manifest.

## [0.23.0] - 2026-10-11

### Added
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# XChain Platform Node

<p align="center">
<img src="https://img.shields.io/badge/version-0.21.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/version-0.27.0-blue" alt="Version">
<img src="https://img.shields.io/badge/tests-4%2C638%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">
</p>
Expand Down Expand Up @@ -77,6 +77,10 @@ xchain-node stop all bitcoin regtest
xchain-node start all bitcoin regtest
```

The `start`, `stop`, `restart`, and `uninstall` commands leave the shared explorer
out when `all` is scoped to one chain, because one explorer serves every chain. Name it directly
when needed, for example `xchain-node restart explorer`.

### Run a validator

By default the bundled `xchain-hub` runs as a standalone config oracle. To run it
Expand Down Expand Up @@ -170,19 +174,19 @@ Turn it off with any of: `--no-telemetry` on any command (sticks for future runs

| Command | Description |
|---|---|
| `npm test` | Unit tests (2,416 tests) |
| `npm run test:integration` | Integration tests (105 tests) |
| `npm test` | Unit tests (3,562 tests) |
| `npm run test:integration` | Integration tests (152 tests) |
| `npm run test:smoke` | Smoke tests (159 tests) |
| `npm run test:boundary` | Boundary condition tests (57 tests) |
| `npm run test:security` | Security tests (149 tests) |
| `npm run test:e2e` | End-to-end tests (57 tests) |
| `npm run test:boundary` | Boundary condition tests (61 tests) |
| `npm run test:security` | Security tests (202 tests) |
| `npm run test:e2e` | End-to-end tests (58 tests) |
| `npm run test:fuzz` | Fuzz tests (264 tests) |
| `npm run test:chaos` | Chaos engineering tests (121 tests) |
| `npm run test:regression` | Regression tests (59 tests) |
| `npm run test:regression:p0` | Regression P0: critical gate (33 tests) |
| `npm run test:regression:p0p1` | Regression P0+P1: standard gate (52 tests) |
| `npm run test:mutation` | Mutation testing (Stryker Mutator) |
| `npm run test:all` | All tests (~3,181 tests; excludes security/boundary) |
| `npm run test:all` | All tests (~4,375 tests; excludes security/boundary) |
| `npm run benchmark` | Performance benchmarks (5 scenarios) |
| `npm run benchmark:quick` | Quick benchmarks |

Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Email **[email protected]** with:
- The affected version (see `CHANGELOG.md` and the version badge in `README.md`) and the environment (host OS, Docker version, which services were installed).
- Any patches or mitigations you'd like considered.

For sensitive reports, encrypt the email body to our PGP key. The fingerprint will be published alongside the first signed release artifact; until then, the email channel is acceptable for first contact and we will coordinate an encrypted exchange before you share proof-of-concept details.
For sensitive reports, the private advisory channel above is the confidential path we recommend: it is private until we publish it and needs no key exchange. If you would rather use email, send a first message without sensitive details and we will coordinate an encrypted exchange before you share them. We do not publish a PGP key for this address. Published release signing keys are provided only to verify downloads; do not encrypt a report to them.

We do not currently offer a paid bug bounty. We do offer public credit in release notes and the advisory itself, unless you prefer to remain anonymous.

Expand Down Expand Up @@ -92,4 +92,4 @@ We ship security fixes against the latest release on `master`. Older releases ar

---

Last reviewed: 2026-06-16.
Last reviewed: 2026-10-30.
30 changes: 15 additions & 15 deletions package-lock.json

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

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xchain-node",
"version": "0.26.3",
"version": "0.27.0",
"private": true,
"description": "xchain-node allows users to install, configure and run XChain platform nodes.",
"license": "AGPL-3.0-or-later",
Expand Down Expand Up @@ -66,6 +66,7 @@
"js-yaml": "^4.3.1",
"form-data": "^4.0.6",
"ip-address": "^10.3.1",
"zod": "^4.6.6",
"decimal.js": "10.4.3",
"@modelcontextprotocol/sdk": "^1.31.0",
"bitcoinjs-message": {
Expand All @@ -77,6 +78,6 @@
"xchain-node": "./src/index.js"
},
"engines": {
"node": ">=22.0.0"
"node": ">=22.0.0 <23"
}
}
4 changes: 3 additions & 1 deletion scripts/publish-bootstraps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ log "publish plan (${#SELECTED[@]}): ${SELECTED[*]}"
[ "$WITH_TRACKERS" = 1 ] && log "NOTE: tracker creates STOP the tracker container (downtime) for the compress."
if [ "$DRY_RUN" = 1 ]; then log "dry-run: nothing executed."; exit 0; fi

mkdir -p "$TMP_DIR"

# ── Single-instance lock (cron-safe) ──────────────────────────────────────
exec 9>"$LOCK_FILE"
flock -n 9 || die "another publish run holds $LOCK_FILE - exiting."
Expand All @@ -373,7 +375,7 @@ for c in "${SELECTED[@]}"; do
# CLI does not also wait and multiply the budget.
lock_deadline=$(( $(date +%s) + LOCK_WAIT_MIN * 60 ))
while : ; do
create_log="$(mktemp)"
create_log="$(mktemp "$TMP_DIR/publish-create.XXXXXX")"
create_rc=0
XCHAIN_NODE_BOOTSTRAP_SIGNING_KEY="$SIGNING_KEY" \
XCHAIN_NODE_DATA_DIR="$STAGE_DIR" \
Expand Down
10 changes: 7 additions & 3 deletions src/cli/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ function registerUninstall(program, deps) {
.argument('[network]', '(mainnet, testnet, regtest, all)')
.option('--include-shared', 'Also uninstall shared services (xchain-explorer, xchain-sync, xchain-hub) once no chain/network is still installed; the database is never removed and must be removed manually')
.action(async (service, chain, network, options) => {
const serviceList = filterCommandParameters(null, service, chain, network)
const serviceList = filterCommandParameters(null, service, chain, network, { includeSharedExplorer: false })
// A module that failed to uninstall must not be printed and forgotten,
// leaving the command exiting 0 with containers still running. The
// remaining modules are still attempted (uninstallModules finishes the
Expand Down Expand Up @@ -329,8 +329,9 @@ function registerExec(program, deps) {
.argument('<command>', 'The shell command to execute')
.action(async (service, chain, network, command) => {
const serviceList = filterCommandParameters(null, service, chain, network)
await execModules(serviceList, command)
return process.exit(0)
// execModules has already printed the reason; scripts chaining on exec need the status.
const ok = await execModules(serviceList, command)
return process.exit(ok === true ? 0 : 1)
})

}
Expand All @@ -350,6 +351,9 @@ function registerClearReorgHalt(program, deps) {
console.log("clear-reorg-halt takes one chain and one network; 'all' is invalid")
return process.exit(1)
}
// Refuse a typo here, before it reaches the container lookup and gets reported as "no container installed".
const bad = [['chain', chain, config.Coin], ['network', network, config.Network]].find(([, v, known]) => !Object.values(known).includes(v))
if (bad) { console.log(`clear-reorg-halt: unknown ${bad[0]} '${bad[1]}'; expected one of ${Object.values(bad[2]).join(', ')}`); return process.exit(1) }
const serviceList = filterCommandParameters(null, 'xchain-decoder', chain, network)
const ok = await clearDecoderReorgHalt(serviceList, {
reason: options.reason, force: !!options.force, dryRun: !!options.dryRun
Expand Down
Loading
Loading