Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .ci-timeout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3600
17 changes: 17 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- Thanks for contributing to XChain Platform. -->

## What this changes

<!-- One or two sentences on what changed and why. Link an issue if there is one. -->

## How it was verified

<!-- What you ran, and what it said. "Tests pass" is worth more with the tally. -->

## Contributor License Agreement

- [ ] I have read and agree to the [Contributor License Agreement](https://docs.xchain.io/legal/cla).

The CLA Assistant bot checks this automatically and records your signature
against your GitHub account, once, covering all XChain Platform repositories.
Its `license/cla` check is the record that counts; this box is a reminder.
42 changes: 3 additions & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,7 @@ jobs:
# deploy keys this used to need are gone. Roster = .ci-siblings, the same
# declaration the e2e venue reads.
- name: Check out declared sibling repositories
env:
SIBLINGS_REF: ${{ github.ref == 'refs/heads/master' && 'master' || 'develop' }}
run: |
set -euo pipefail
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/${{ github.repository_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)"
# Two guards require the indexer's modules, not just its files, so a
# source-only checkout fails on the sibling's own dependencies
# (Cannot find module 'mathjs'). Runtime deps only, never fatal.
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
uses: XChain-Platform/.github/actions/checkout-siblings@master # one definition for every call site; see the action for why this tracks master

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

Expand Down Expand Up @@ -209,24 +190,7 @@ jobs:
# ratchet measures a suite the gate never ran: 1668 of 1745 unit tests ran without them.
# Same roster (.ci-siblings) and same layout the shared workflow uses.
- name: Check out declared sibling repositories
env:
SIBLINGS_REF: ${{ github.ref == 'refs/heads/master' && 'master' || 'develop' }}
run: |
set -euo pipefail
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/${{ github.repository_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"
fi
done
uses: XChain-Platform/.github/actions/checkout-siblings@master # one definition for every call site; see the action for why this tracks master

- name: Use Node.js 22
uses: actions/setup-node@v4
Expand Down
156 changes: 156 additions & 0 deletions bin/ci-full.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env bash
#*********************************************************************
#
# Copyright © 2025-2026 Dankest, LLC
# Based on XChain Platform by Dankest, LLC - https://dankest.llc
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This file is part of XChain Platform. Licensed under the GNU Affero
# General Public License v3.0 or later; see LICENSE.md. A commercial
# license (without AGPL source-disclosure terms) is available -
# contact legal@dankest.llc.
#
#*********************************************************************

#
# bin/ci-full.sh: run EVERY tier this repo's GitHub CI runs, in one process.
#
# .github/workflows/ci.yml fans this repo out as four jobs (ci, e2e,
# drift-guards, coverage). The pre-push venue gate used to run only
# `npm run ci`, so a push could gate green locally and then go red on GitHub
# on a job the gate never ran (2026-08-15: exactly that, on three repos at
# once). This script IS the local twin of the workflow: every job's run
# steps, transcribed, in job order. When ci.yml gains or changes a job,
# change this script in the same commit.
#
# Layout: siblings resolve at ../<repo>, which is both the platform monorepo
# layout and the venue gate's work/ layout (.ci-siblings ships them there). A
# sibling a GitHub job checks out is REQUIRED here: missing means fail loud,
# never skip, because GitHub will run the step this gate would be skipping.
#
# The e2e job spins up its OWN throwaway MariaDB pair (source-db + replica-db,
# test/e2e/docker-compose.e2e.yml) rather than a venue-provided database,
# matching the two GitHub Actions service containers exactly (same image,
# ports, and fixture credentials). It needs Docker; a docker-less venue fails
# this script loud rather than silently skip the tier GitHub actually runs.
#
# SKIPPED-BY-DESIGN: none. Every real test/build step ci.yml runs is
# transcribed below (checkout/setup-node/npm-ci/cache steps are GitHub-only
# bookkeeping and need no transcription).
#
# All tiers run even after one fails (GitHub reports every red job, so this
# reports every red tier); the exit code is red if any tier was.
#
set -uo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/.."
SELF="$(pwd)"
SIB="$(cd .. && pwd)"

FAILED=""
run_tier() {
local name="$1"; shift
echo; echo "ci:full ===== $name ====="
if "$@"; then
echo "ci:full ----- $name PASS"
else
FAILED="$FAILED [$name]"
echo "ci:full ----- $name FAIL"
fi
}
need_sib() {
local s
for s in "$@"; do
if [ ! -d "$SIB/$s" ]; then
echo "ci:full: MISSING SIBLING $SIB/$s" >&2
echo "ci:full: GitHub CI checks this sibling out and runs steps against it," >&2
echo "ci:full: so skipping here would gate green on a subset. Declare it in" >&2
echo "ci:full: .ci-siblings (venue) or clone it beside this repo (hand run)." >&2
exit 1
fi
done
}

export XCHAIN_INDEXER_SQL_PATH="${XCHAIN_INDEXER_SQL_PATH:-$SIB/xchain-indexer/src/sql}"
export XCHAIN_DECODER_SQL_PATH="${XCHAIN_DECODER_SQL_PATH:-$SIB/xchain-decoder/src/sql}"

need_sib xchain-indexer xchain-decoder xchain-hub

# The e2e job (below) needs Docker for its two service containers; guard once,
# up front, so a docker-less venue fails loud instead of every DB-backed tier
# failing separately with a confusing connection-refused error.
docker info >/dev/null 2>&1 || {
echo "ci:full: VENUE LACKS DOCKER for e2e job (source-db/replica-db service" >&2
echo "ci:full: containers, e2e tier, integration tier); pin a docker venue" >&2
echo "ci:full: with CI_VENUES=..." >&2
exit 1
}

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

# --- job: e2e ----------------------------------------------------------------
# GitHub stands up source-db (:23306) and replica-db (:23307) as service
# containers before any step runs; test/e2e/docker-compose.e2e.yml is the
# same pair (same image, ports, MARIADB_USER/PASSWORD), and every e2e/testDb
# 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_compose_down() {
docker compose -f "$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

# 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
# silently skip there. Run them HERE, where xchain-indexer and xchain-decoder
# ARE checked out, with XCHAIN_REQUIRE_SIBLINGS=1 so a missing sibling
# hard-fails instead of green-by-skip. Pure source comparisons (no DB).
run_tier "e2e: cross-repo consensus drift guards" \
env XCHAIN_REQUIRE_SIBLINGS=1 \
npx mocha --timeout 10000 \
test/unit/rollback-coverage.test.js \
test/unit/blockhash-conformance-twin.test.js \
test/unit/protocolAddressRoles.twin.test.js \
test/unit/stakesValidatorSetParity.test.js \
test/unit/generatedColumns.test.js

run_tier "e2e: e2e tier (test:e2e:ci)" npm run test:e2e:ci

# Independent of the e2e tier above (own DBs, own schema seed); run even if
# the e2e tier failed, so a flake there can't mask the integration result.
# 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 \
npm run test:integration:ci

run_tier "e2e: tear down service containers" e2e_compose_down
trap - EXIT

# --- job: drift-guards -------------------------------------------------------
# Run FROM the parent so sync-coins.sh sees the canonical + vendored pair the
# way the workflow lays them out (hub checkout beside this repo's checkout).
sync_coins_check() { (cd "$SIB" && "xchain-hub/bin/sync-coins.sh" --check --only "$(basename "$SELF")"); }
run_tier "drift-guards: coin-registry byte-identity" sync_coins_check
run_tier "drift-guards: coin consensus-pin conformance" node -e '
const coins = require("./src/coins");
for (const net of ["testnet", "regtest"]) {
const res = coins.verifyConsensusPin(net);
if (res && res.skipped) throw new Error("consensus pin unexpectedly unarmed for " + net);
}
console.log("consensus pin conformance OK (testnet, regtest)");
'

# --- job: coverage -----------------------------------------------------------
run_tier "coverage ratchet (coverage:check)" npm run coverage:check

echo
if [ -n "$FAILED" ]; then
echo "ci:full: RED tiers:$FAILED"
exit 1
fi
echo "ci:full: all tiers green (same set GitHub CI runs)"
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"test:mutate": "npx stryker run test/mutation/stryker.config.json",
"test:mutate:quick": "npx stryker run test/mutation/stryker.quick.config.json",
"test:mutate:check": "npx stryker run test/mutation/stryker.config.json --incremental",
"test:boundary": "mocha --timeout 5000 --recursive 'test/boundary/**/*.boundary.test.js'"
"test:boundary": "mocha --timeout 5000 --recursive 'test/boundary/**/*.boundary.test.js'",
"ci:full": "bash bin/ci-full.sh"
},
"overrides": {
"brace-expansion": "^5.0.9",
Expand Down
2 changes: 1 addition & 1 deletion src/TransparencyLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class TransparencyLog {
}
}

// OPT-IN retention for the per-block sync_meta rows (XC-1363). DEFAULT OFF: with
// OPT-IN retention for the per-block sync_meta rows. DEFAULT OFF: with
// SYNC_META_RETENTION_BLOCKS unset (or 0) this is a no-op and the log keeps full
// history, which stays the shipped behaviour. Mirrors the indexer's
// retention.pruneStateRoots (xchain-indexer/src/retention.js): a positive window
Expand Down
2 changes: 1 addition & 1 deletion src/balance-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function minimalSupply(sumExpr, decimals) {
//
// Each ledger row is summed at the EXACT ledger scale (DECIMAL(60,18)) and the TOTAL
// is rounded ONCE to the token's own decimals, mirroring the indexer's exact-ledger
// rule (XC-1459, xchain-indexer/src/ledger_amount_precision_activation.js). The
// rule (xchain-indexer/src/ledger_amount_precision_activation.js). The
// former shape cast each ROW to DECIMAL(60,d) first, which agreed with the indexer
// only while every stored amount already sat on the token's grid; once the indexer
// stores fee amounts finer than the tick (0.5 XCHAIN against a decimals=0 gas tick),
Expand Down
8 changes: 5 additions & 3 deletions src/sql/state_tree_nodes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ CREATE TABLE state_tree_nodes (
left_hash CHAR(64) NOT NULL, -- the two child hashes (a child may be an EMPTY constant)
right_hash CHAR(64) NOT NULL,
UNIQUE KEY uq_node_hash (node_hash),
KEY idx_left_hash (left_hash), -- reachability sweep for the (deferred) pruner
KEY idx_left_hash (left_hash), -- reachability sweep for the opt-in pruner
KEY idx_right_hash (right_hash)
-- Content-addressed, copy-on-write SMT node store for the light-client state
-- commitment (SPV spec §4.1/§4.3). Holds INTERNAL nodes only (depth 0..255);
-- a value leaf (depth 256) is never its own row, it lives as a child hash of
-- its depth-255 parent. Empty subtrees (the EMPTY[h] constants) are never
-- stored. Append-only during forward processing (INSERT IGNORE; identical
-- subtrees dedupe by hash). Reorg leaves orphaned nodes here that a later
-- mark-and-sweep prunes; rollback only drops the state_tree_roots pointers.
-- subtrees dedupe by hash). Reorg leaves orphaned nodes here that the opt-in
-- mark-and-sweep pruner in the indexer's retention.js reclaims, gated by
-- STATE_ROOT_RETENTION_BLOCKS plus STATE_NODE_RECLAIM; rollback only drops
-- the state_tree_roots pointers.
-- Never written by hub_db_sync.
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
Loading
Loading