Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
fdf7a0f
fix(reorg): honor Litecoin testnet depth and halt status
jdogresorg Sep 17, 2026
7d0ea1c
test(e2e): TRUST_PROXY forwarded headers have a regression test and t…
jdogresorg Sep 17, 2026
cefb9cb
ci: sibling checkouts follow the pull request's base branch, keeping …
jdogresorg Sep 17, 2026
10c66f9
test(sync): split trust-proxy e2e callback under the function length …
jdogresorg Sep 17, 2026
c58a797
test: split observability suite registration
jdogresorg Sep 17, 2026
baedc40
fix(sync): scope missing-table warnings to source schema
jdogresorg Sep 17, 2026
f691f7a
fix(consensus): re-cut the LTC and DOGE mirror-admission heights onto…
jdogresorg Sep 17, 2026
1e2c5f9
test(sync): split balance helper registrations
jdogresorg Sep 17, 2026
058215c
test(sync): split hash continuity registrations
jdogresorg Sep 17, 2026
edb99a6
test(sync): split config boundary registrations
jdogresorg Sep 17, 2026
e1b239d
fix(sync): carry an edited tokens row to the replica in the block the…
jdogresorg Sep 19, 2026
c26aa9a
merge: level develop with master for the v0.20.1 patch train
jdogresorg Sep 19, 2026
e091b6e
fix(protocol): ship LTC:testnet mirror admission as null under dq4 (a)
jdogresorg Sep 19, 2026
e3b6f7c
fix(consensus): re-slide v0.20.1 BTC and DOGE activation heights
jdogresorg Sep 19, 2026
5fd36c0
test: parameterize fixture host ports
jdogresorg Sep 18, 2026
fa86b9a
refactor(observability): take the shared shim without underscore names
jdogresorg Sep 14, 2026
5feee57
test: split proxy rate limit security coverage
jdogresorg Sep 15, 2026
8ae06d2
test: split API security coverage by behavior
jdogresorg Sep 15, 2026
2a3b3c4
test: split broadcaster security coverage by behavior
jdogresorg Sep 15, 2026
0fadb1b
test: split scoped balance rebuild suite by behavior
jdogresorg Sep 15, 2026
657ff49
test: split server polling integration suite by behavior
jdogresorg Sep 15, 2026
1956ae9
refactor(sync): extract armed source handshake fixtures
jdogresorg Sep 15, 2026
e2c9da9
refactor(sync): extract binary replication fixtures
jdogresorg Sep 15, 2026
cc289e5
refactor(sync): extract emissions parity assertions
jdogresorg Sep 15, 2026
bbe87e7
refactor(sync): extract HTTP parity integration setup
jdogresorg Sep 15, 2026
812cdbc
test: split circuit breaker boundary suite by behavior
jdogresorg Sep 15, 2026
4fac713
test: split config parsing boundary suite by behavior
jdogresorg Sep 15, 2026
6353fba
test: split hash continuity boundary suite by behavior
jdogresorg Sep 15, 2026
e699cc8
test: split hub port parsing boundary suite by behavior
jdogresorg Sep 15, 2026
b2d49e4
test: split poll limit boundary suite by behavior
jdogresorg Sep 15, 2026
5d93503
test: split reorg detection boundary suite by behavior
jdogresorg Sep 15, 2026
3e11843
test: split transparency pagination boundary suite by behavior
jdogresorg Sep 15, 2026
3de1f46
test: split websocket limits boundary suite by behavior
jdogresorg Sep 15, 2026
4aed5fd
refactor(db): move the applier and updated-rows SQL into db mixins
jdogresorg Sep 14, 2026
53f7070
refactor(config): read the remaining environment through the config home
jdogresorg Sep 14, 2026
7209373
docs(comments): restore the ClientApplier method summaries
jdogresorg Sep 14, 2026
71cb8f3
refactor(naming): drop the underscore from the stake-weight SQL builders
jdogresorg Sep 14, 2026
a40990d
fix(db): point tables.js at the renamed consensus/state_hash module
jdogresorg Sep 20, 2026
8fb8e4d
docs(ci): correct .ci-siblings dispatcher comment to match ci-dispatc…
jdogresorg Sep 18, 2026
25b2215
Re-vendor the observability shim from canonical
jdogresorg Sep 21, 2026
aed0702
Update observability reset test hook
jdogresorg Sep 21, 2026
7e0665a
Land row SYNC-OBS-RESET from cx-sync-observability-reset
jdogresorg Sep 21, 2026
5ba735b
fix(sync): reconcile unstable index status ids
jdogresorg Sep 21, 2026
26dad19
Merge: reconcile unstable index status ids
jdogresorg Sep 21, 2026
7d0efc2
docs(comments): reflow scrubbed explanations
jdogresorg Sep 21, 2026
2416411
Merge: docs(comments): reflow scrubbed explanations
jdogresorg Sep 21, 2026
d4a119b
test(sync): stub indexer util in rollback coverage guard
jdogresorg Sep 21, 2026
e993eb2
Merge: test(sync): stub indexer util in rollback coverage guard
jdogresorg Sep 21, 2026
4b1078d
test(sync): drop dead fixture-credential helper, de-namify hub_client…
jdogresorg Sep 22, 2026
77706f7
chore(observability): vendor the hub canonical module, unmatched-rout…
jdogresorg Sep 22, 2026
cb1c908
refactor(applier): name the incremental snapshot table loop as its ow…
jdogresorg Sep 22, 2026
6c972a6
build(ci): grade the fast tier on a push, defer the heavy tiers to th…
jdogresorg Sep 22, 2026
02b1d28
fix(sync): own-property lookup for generatedColumns table names
jdogresorg Sep 22, 2026
1d9c3fe
Merge: fix(sync): own-property lookup for generatedColumns table names
jdogresorg Sep 22, 2026
60ae9d5
fix(test): independent FK oracle for tier1 applier fuzz suite
jdogresorg Sep 22, 2026
73c718c
docs(sync): align the bridge_transfers table-lifecycle note with the …
jdogresorg Sep 23, 2026
5dba4de
test(sync): cover /halt/clear auth, the WS upgrade guard, and the che…
jdogresorg Sep 23, 2026
83f284a
test(sync): check every carrier twin instead of skipping on the first…
jdogresorg Sep 23, 2026
5d26097
fix(sync): populate the dispenser reconcile knobs, arm the wall-clock…
jdogresorg Sep 23, 2026
85f5818
test(sync): wait on named observables in the chaos suites, correct tw…
jdogresorg Sep 23, 2026
af6d3e8
fix(sync): halt durably on a repeating markets duplicate-key loop, ad…
jdogresorg Sep 23, 2026
d04ba91
fix(sync): check schema_version on every dispensers reconcile page, r…
jdogresorg Sep 23, 2026
865a594
Merge: fix(test): independent FK oracle for tier1 applier fuzz suite
jdogresorg Sep 23, 2026
d333861
Merge: test(sync): cover /halt/clear auth, the WS upgrade guard, and …
jdogresorg Sep 23, 2026
846ecc5
fix(consensus): re-slide v0.20.1 BTC and DOGE activation heights, 40h…
jdogresorg Sep 23, 2026
b98aa70
chore(release): v0.20.1
jdogresorg Sep 23, 2026
834a300
chore: remove internal references from public source
jdogresorg Sep 23, 2026
a7931d0
fix(sync): refold an orphaned ISSUE edit off the replica tokens row o…
jdogresorg Sep 23, 2026
2b356a9
fix(consensus): re-slide DOGE:testnet mirror admission to the 84h tra…
jdogresorg Sep 23, 2026
32f00dc
Merge branch 'cut/v0201-dq4' into cut/v0201-land
jdogresorg Sep 23, 2026
bf11345
Merge branch 'cut/sync-token-rollback' into cut/v0201-land
jdogresorg Sep 23, 2026
516affd
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
6 changes: 3 additions & 3 deletions .ci-siblings
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
# test/unit/sibling-coverage.test.js, which reports the surface this file leaves
# uncovered; the same gap applies to a sibling that is declared but missing.
#
# ci-dispatch.sh ships each of these at its origin/master (falling back to local
# HEAD) and REFUSES the push if one is not a checkout beside this repo, so a
# declared sibling can never be silently absent.
# ci-dispatch.sh ships each of these at the pushed branch (falling back to its
# origin/master, then local HEAD) and REFUSES the push if one is not a checkout
# beside this repo, so a declared sibling can never be silently absent.

# Consensus-primitive conformance and the state-commitment reference.
xchain-documentation
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@6f4d39ae85787fc31e90a31588d87610a2c33103 # pin: XChain-Platform/.github @ master 2026-08-14; bump deliberately
with:
# A hotfix or release PR compares against siblings at the same branch, not develop.
siblings-ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || '' }}
siblings-ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || github.base_ref || '' }}
# Override the Node version for a repo if ever needed:
# node-version: "20"

Expand Down Expand Up @@ -78,7 +78,7 @@ jobs:
- name: Check out declared sibling repositories
uses: XChain-Platform/.github/actions/checkout-siblings@master # one definition for every call site; see the action for why this tracks master
with:
ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || '' }}
ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || github.base_ref || '' }}

- name: Use Node.js 22
uses: actions/setup-node@v4
Expand Down Expand Up @@ -154,7 +154,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: XChain-Platform/xchain-hub
ref: ${{ github.ref == 'refs/heads/master' && 'master' || 'develop' }}
ref: ${{ github.base_ref || (github.ref == 'refs/heads/master' && 'master' || 'develop') }}
ssh-key: ${{ secrets.XCHAIN_HUB_DEPLOY_KEY }}
path: xchain-hub

Expand Down Expand Up @@ -214,7 +214,7 @@ jobs:
- name: Check out declared sibling repositories
uses: XChain-Platform/.github/actions/checkout-siblings@master # one definition for every call site; see the action for why this tracks master
with:
ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || '' }}
ref: ${{ (startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) && github.head_ref || github.base_ref || '' }}

- name: Use Node.js 22
uses: actions/setup-node@v4
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ 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 and DOGE testnet activations with LTC inert, and made replication halt durably on repeated market key conflicts.


## [0.20.0] - 2026-09-17

### Changed
Expand Down
14 changes: 8 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Pinned to node:22-bookworm, the tag .nvmrc and package.json engines already
# declare and the sibling service images already build on. `node:latest` floats:
# xchain-node rebuilds this image on every update (ModuleService.buildAndUp), so
# a routine rolling upgrade silently moves the runtime off the declared Node 22
# with no signal anywhere.
FROM node:22-bookworm
# Pinned by digest to the node:22.23.2-bookworm image whose V8/ICU build match
# xchain-vm's consensus runtime pin: the floating node:22-bookworm tag moved to
# a Node 22 patch whose V8/ICU no longer match, so validators built from it
# would fail checkConsensusRuntime(). `node:latest` floats too: xchain-node
# rebuilds this image on every update (ModuleService.buildAndUp), so a routine
# rolling upgrade silently moves the runtime off the declared Node 22 with no
# signal anywhere.
FROM node:22.23.2-bookworm@sha256:dd5847a04b0deee391fa145f1f4c6d214196668b6bcc7988ebed67249f226844

RUN mkdir /XChainIndexerSync/
COPY ./package.json /XChainIndexerSync/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 Sync

<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-2%2C779%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
49 changes: 45 additions & 4 deletions bin/ci-full.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,35 @@ 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=(
"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 @@ -97,12 +125,13 @@ run_tier "ci" npm run ci
# helper already defaults to those ports and credentials, so no env override
# is needed once the stack is up.
E2E_COMPOSE="test/e2e/docker-compose.e2e.yml"
E2E_DB_PORT_RESOLVED="$(node bin/fixture-ports.js port E2E_DB_PORT)" || exit 1
e2e_compose_down() {
docker compose -f "$E2E_COMPOSE" down -v >/dev/null 2>&1
node bin/fixture-ports.js compose "$E2E_COMPOSE" down -v >/dev/null 2>&1
}
trap e2e_compose_down EXIT
run_tier "e2e: bring up service containers (source-db, replica-db)" \
docker compose -f "$E2E_COMPOSE" up -d --wait
node bin/fixture-ports.js compose "$E2E_COMPOSE" up -d --wait

# Cross-repo consensus drift guards (rollback-coverage and friends) live in
# the unit tier but the shared `ci` job never checks out a sibling, so they
Expand All @@ -125,7 +154,7 @@ run_tier "e2e: e2e tier (test:e2e:ci)" npm run test:e2e:ci
# Reuses source-db (:23306) with the admin credentials, not the e2e
# xchain-node user, matching the workflow step exactly.
run_tier "e2e: integration tier (green suites, test:integration:ci)" \
env TEST_DB_HOST=127.0.0.1 TEST_DB_PORT=23306 TEST_DB_USER=root TEST_DB_PASS=test \
env TEST_DB_HOST=127.0.0.1 TEST_DB_PORT="$E2E_DB_PORT_RESOLVED" TEST_DB_USER=root TEST_DB_PASS=test \
npm run test:integration:ci

run_tier "e2e: tear down service containers" e2e_compose_down
Expand Down Expand Up @@ -156,8 +185,20 @@ run_tier "identity pin (armed map, vendored coins)" node bin/pin-identity.js --c
run_tier "coverage ratchet (coverage:check)" 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 <<<
106 changes: 106 additions & 0 deletions bin/fixture-ports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const REPO = path.join(__dirname, '..');
const SPECS = Object.freeze({
'test/e2e/docker-compose.e2e.yml': Object.freeze({
E2E_DB_PORT: 23306,
E2E_REPLICA_DB_PORT: 23307,
E2E_SOURCE2_DB_PORT: 23308
}),
'test/chaos/fixtures/docker-compose.chaos.yml': Object.freeze({
SOURCE_DIRECT_PORT: 33065,
TOXIPROXY_PORT: 8474,
SOURCE_PROXY_PORT: 33060,
REPLICA_PROXY_PORT: 33061,
WS_PROXY_PORT: 33062
})
});

function integer(value, label) {
const text = String(value);
if (!/^(0|[1-9][0-9]*)$/.test(text)) {
throw new Error(`${label} must be a non-negative integer, got ${JSON.stringify(text)}`);
}
return Number(text);
}

function offset(env) {
const source = env || process.env;
return source.CI_PORT_OFFSET === undefined || source.CI_PORT_OFFSET === ''
? 0
: integer(source.CI_PORT_OFFSET, 'CI_PORT_OFFSET');
}

function basePort(name) {
for (const spec of Object.values(SPECS)) {
if (Object.prototype.hasOwnProperty.call(spec, name)) return spec[name];
}
throw new Error(`unknown fixture port ${name}`);
}

function port(name, env) {
const source = env || process.env;
const value = source[name] === undefined || source[name] === ''
? basePort(name) + offset(source)
: integer(source[name], name);
if (value < 1 || value > 65535) throw new Error(`${name} resolves outside the TCP port range: ${value}`);
return value;
}

function composeEnvironment(file, env) {
const spec = SPECS[file];
if (!spec) throw new Error(`unknown fixture compose file ${file}`);
const result = { ...(env || process.env) };
for (const name of Object.keys(spec)) result[name] = String(port(name, result));
return result;
}

function render(file, env) {
const spec = SPECS[file];
if (!spec) throw new Error(`unknown fixture compose file ${file}`);
let source = fs.readFileSync(path.join(REPO, file), 'utf8');
for (const [name, base] of Object.entries(spec)) {
source = source.split(`\${${name}:-${base}}`).join(String(port(name, env)));
}
return source;
}

function compose(file, args, env) {
const result = spawnSync('docker', ['compose', '-f', file, ...args], {
cwd: REPO,
env: composeEnvironment(file, env),
stdio: 'inherit'
});
if (result.error) throw result.error;
return result.status === null ? 1 : result.status;
}

function main(argv) {
const [command, name, ...args] = argv;
if (command === 'render' && name && args.length === 0) {
process.stdout.write(render(name));
return 0;
}
if (command === 'port' && name && args.length === 0) {
process.stdout.write(String(port(name)) + '\n');
return 0;
}
if (command === 'compose' && name && args.length > 0) return compose(name, args);
throw new Error('usage: fixture-ports.js <render FILE|port NAME|compose FILE ARGS...>');
}

if (require.main === module) {
try {
process.exitCode = main(process.argv.slice(2));
} catch (err) {
console.error(err.message);
process.exitCode = 1;
}
}

module.exports = { SPECS, offset, port, composeEnvironment, render };
Loading
Loading