Release v0.19.0 - xchain-node - #45
Merged
Merged
Conversation
Re-vendors the shared coin bundles with the bridge escrow addresses.
…clear the price fence on an older hub A GitHub rate limit or an unreachable network on the remote node version check now prints one warning per status pass and continues, instead of aborting every update. The reset command falls back to the chain-only fence delete when the hub predates the network column, and any other SQL failure still propagates.
The package.json and lockfile still pinned xchain-sdk 0.11.1, several trains behind the published 0.18.0 the node ships beside. The bump is a registry install with a fresh integrity hash and needs no source change, since the node's SDK call sites are untouched by any change in that range and the suite stays green.
BootstrapService mapped an inspection FAILURE to FRESHNESS_EMPTY, breaking its own stated invariant that only a positive finding of emptiness may authorise a destructive restore: the alpine listing returned the pipeline's exit status so a failed listing read as an empty tracker store, and a null container id was treated as confirmed absence. Also hardens the command lock's stale reclamation, the module build-and-up status race, and the decoder/indexer coupling guard in resetModules. From the 2026-09-12 review round.
Lets the operator override the shared hub's published and internal port via host env, mirroring the existing explorer port override. Adds unit tests covering both the override and the default-unchanged path.
The indexer moved the module ConfigService names into a feature directory, so the path literal follows it. Nothing else in the service changes.
…CLI print text Three pins make a restructure checkable: the test script's files mapped to their sorted titles, the sha256 of the vendored coin registry files, and the user-facing print text with every command's driven help output. The tools that write and compare them are added under bin/, and the unit tier's wall time is recorded beside them.
bin/reachability.js walks every runtime, tooling and test entry point and sweeps the sibling checkouts for references, and bin/pins/dead-code-sweep.txt records its verdict that every tracked src file is still held by something that runs. The sibling sweep reads this repo's name from one constant, so it reports references to this repo's own paths.
src/MariaDbStore.js becomes src/db/index.js with the pool and readiness guards, and the registry queries move verbatim into src/db/modules.js, installed on the prototype so call sites are unchanged. Three methods take the fixed verb prefixes: insertModuleContainer becomes setModuleContainer, removeModuleContainer becomes deleteModuleContainer and countModules becomes getModuleCount.
…/db/ Every SQL statement the services held inline moves verbatim, with the comment that explains it, into a file under src/db/ named for the table family it reads, and each service calls the named builder. The only SQL text left outside src/db/ is the manual DELETE statement the operations layer prints for an operator to run by hand.
eslint.config.js carries the shared preset's rules inline, turns no-console off for the four user-facing print paths and scripts/, and lifts the process.env restriction inside src/config/. eslint 9 is added to devDependencies through the lockfile.
The connectors, the downloader and the secret-env module join src/services/ and src/config/, the config constants become src/config/index.js, and every src and test file takes a snake_case name, with the references, proxyquire specifiers and stub keys each move forces. bin/pins/suite-title-renames.json declares every moved path and the test titles that changed with the renamed db methods and module names.
Both operator commands require a service module by path and name it again in a comment, and both now use the snake_case names.
…the logger Named environment reads across the services become getters on src/config/, evaluated when the caller runs, and HUB_PORT_OVERRIDE gets its own name because HUB_PORT is also a constant with a different meaning. src/observability/logger.js is added, and test/helpers/config_stub.js copies property descriptors so a stubbed config still answers through its getters.
…the CLI's prints alone Console calls in the service layer now go through src/observability/logger.js, which falls through to console so the output and every suite reading it are unchanged. The four user-facing print paths (src/cli.js, src/operations/, src/ui/ and src/precheck.js) keep printing raw, and their text is unchanged.
_readCredentialsRaw, _pingMariaDb and _getHashForArch become plain names with their call sites, suite titles and comments, and the title rename map declares the changed titles. _call in the hub connector keeps its name because suites in other repos extract it by name.
The GitHub API helpers move to src/utils/github_api.js, which the downloader, the version service and the release manifest service all import. The telemetry default endpoint stays a property of its class rather than a second export beside it.
Comment runs that earlier reference-scrubbing commits deleted come back at each file's current path, only where the code they explain still exists and carries no comment of its own. Restored prose names the renamed files and methods, and comments are the only change.
Where a scrub rewrote a comment and dropped its reasoning along with an internal reference, the fuller explanation replaces the rewrite with the reference removed. Comments are the only change.
A few deleted or condensed comments get their fuller wording back, merged with the current sentence, and the secret-env header regains its paragraph structure. Comments are the only change.
The first two paragraphs of the secret-env header return to their original wording and paragraph breaks. Comments are the only change.
Two stanzas that moved into src/db/ with their queries read as they did beside the code they left, except that the epoch stanza now says the caller rather than this module. Comments are the only change.
…anguage line Each validation check in the hub service has a one-line comment above it stating the rule it guards. Comments are the only change.
… configs The main and pilot stryker configs name source and test files by path, and those entries now use the snake_case paths so every mutate target and spec glob resolves.
…ugh the config home The named variable reads still left in the service layer now go through getters on src/config/, each evaluated when the original expression ran, with its default kept at the call site. The encoder maintenance sentinel path is still read once when its module loads.
…re cycle forbids it Function-body requires whose target sits in no require cycle with the caller now read from a module object bound at the top of the file, destructured where it is used so each call still sees the export as it stands. The requires between members of the service require cycle stay in their function bodies, as does the autoheal command's, since hoisting it would move a line number the CLI prints in a failure trace.
…their files Comments that name xchain-encoder's maintenance window module and xchain-utxo-tracker's bootstrap recovery, LevelDB store and memory budget modules now use those files' current paths. Comments are the only change.
The hub renamed its secret environment table to src/secret_env.js and moved its engines into feature directories. The cross-repo table test and the comments that cite hub files now name the new paths.
The canonical coin registry repointed a consensus note at the decoder's renamed crypto networks module. This re-vendors that byte change so the copy stays identical to canonical.
Presence of the sidecar repo and presence of the file it pins are now checked separately: a checked-out sibling with no table at the pinned path fails the assertion and names the path, instead of silently matching the no-sibling skip. XCHAIN_REQUIRE_SIBLINGS=1 turns the no-sibling case into a failure too, matching the sibling-strict convention used elsewhere in this repo.
A dry run writes nothing, so it should not require a reason; a real clear still refuses one under 8 characters before touching any container. The --reason option is no longer required, and the decoder command gets --reason only when a reason was given.
…to parts beside the node service
…he split database module
Move literal names and service metadata into focused config modules. Preserve the existing config entry path, export order, and exported object identities.
Move installed-module loading and status reducers into focused parts while preserving entry exports. Decompose status table construction into small helpers and retain proxyquire dependency rebinding. Resolve peer services from the services directory in the two parts one level below it, matching the coin node build part's resolution shape.
Move checkout, Docker argument, network and container-build responsibilities into focused service parts. Split long install and build flows into named helpers while preserving the entry API and test behavior.
Move ordered behavior groups into numbered test files with shared scoped fixtures. Titles, assertions, comments, stubs, and both execution modes are preserved.
…ayout-aware helper The indexer keeps tracked migrations under src/db/sql/migrations/ in bucket directories after its SQL home moved, but a decoder checkout or an indexer ref cut before that move still keeps them flat under src/sql/migrations/. The deploy precondition guard previously assumed the flat layout only, so it silently read an empty directory and waved a gated deploy through on a bucketed tree. Add src/utils/migration_files.js with migrationsDirOf (picks the moved home when a checkout carries it, otherwise the flat one) and migrationFiles (lists .sql files flat or one bucket down, sorted by basename, since a schema_migrations row is keyed by basename alone). migration_precondition_service.js now reads through these helpers instead of a single fs.readdirSync call.
Split oversized callbacks into consecutive same-title behavior blocks. Preserve test titles, execution order, assertions, comments, and async boundaries.
… build carries The migration precondition guard's refusal message told an operator to run `node src/migrate.js --file <name>` inside the container being replaced, but the indexer moved that CLI into src/migration/migrate.js. A container built before the move still carries the old path; a container built after carries the new one. The guard was reading the running container's CLI source only at the old path, so a moved-CLI container's probe always came back empty and the guard fell back to refusing --file support entirely. Add src/utils/indexer_migrate_cli.js: readMigrateCli tries the moved path first, falling back to the pre-move path, and remembers per container which path answered so a later refusal message can name it. migrateCliPathFor returns that remembered path, defaulting to the newest layout when nothing has been read yet.
Move validator path, wallet, signer, capability layout, init, settings, capability drift and roll-call responsibilities into focused parts beside the entry. Split capability drift reporting and validator initialization into named helpers while preserving the entry API, export order and test behavior. The DOGE signer template keeps its generated environment lookup in its original literal form so the written script matches what a deployed validator already runs.
Group migration file tests into consecutive same-title suites. Keep temporary directory hooks local to each suite and share only the synchronous file writer.
…m-tree guard its own switch Three suites threw "XCHAIN_REQUIRE_SIBLINGS=1 but ... not checked out" on GitHub (run 35020607785) because .ci-siblings named only xchain-hub. The migration-precondition scan and the ROLLCALL arming mirror now get the indexer staged beside the checkout. The secret-env audit-gate agreement reads the PLATFORM tree, which is private and sits as this checkout's parent, a layout no roster line can stage: it now hard-fails only under XCHAIN_REQUIRE_PLATFORM=1 (exported by the platform venue's bin/ci-all.sh) and otherwise reports a visible PENDING with the reason, never a pass. The arming mirror also moves off the text of src/rollcall_activation.js, which the indexer registry push turned into a shim, and reads the literals from the addGate rows in src/protocol_changes/shared_rows*.js; a missing row or file is a thrown error under the env, not a null-equals-null pass.
Version 0.19.0, the 0.19.0 changelog section and the README badges and script-table counts measured on Linux; the release manifest is cut separately, last.
…atrix legs Since xchain-e2e-test 8df86bb the only XCHAIN gas path onto a non-BTC chain is the bridge: the suite MINTs on BTC and locks it across with an XBRIDGE v0 (gasHelper.bridgeGasIn). A single-coin stack has no bitcoin hub config, so the litecoin and dogecoin legs of every dispatch since 2026-09-12 failed in initialCheck's beforeAll with "[chainRail] hub has no config for bitcoin/regtest" (runs 35102837148 and 35102839982; last green legs 34690792431, three hours before that commit). The two non-BTC legs now install the bitcoin regtest stack after the coin under test, at the same ref. Two stacks on one runner need distinct host ports (the default block collides at the second install's port check), so the leg writes config/<coin>-regtest and config/bitcoin-regtest with the blocks chainRail's DEFAULT_PORTS already expects, and BTC_SERVICE_HOST points the e2e container at the docker bridge gateway, the address the spawned containers already reach the runner's MariaDB on. Ports and that host only; credentials keep coming from the install's own .local sidecars. The bitcoin leg is unchanged. The test drives both step scripts under bash with a recording node stub and asserts the install calls, their order and the file contents, per leg.
Every file under config/ is gitignored, so actions/checkout materializes no such directory and the ports step died on its first dispatch (run 35108669606: "config/litecoin-regtest: No such file or directory"). The test had created the directory in its scratch checkout, which is why it stayed green; it now runs the step in a checkout with no config/, exactly as the runner does, and the step creates the directory itself.
mariadb:11 defaults max_connections to 151. Every service holds a pool of about ten (indexer, decoder, hub, the explorer's per-coin pools, the e2e runner's pool per rail); one coin stack fits under that and two do not. The first two-stack litecoin leg (run 35109600216) booted both stacks, the hub served bitcoin and litecoin, chainRail built the BTC rail and funded the BTC address, then died opening the BTC rail's indexer pool with "Too many connections". The CI DB is per-run and runner-local, so the cap protected nothing; 1000 is headroom.
…seeded local validator The two-stack legs gas in over the bridge lock from BTC, and a standalone hub never finalizes that transfer: startCrossChain returns before constructing CrossChainBridgeEngine without a peerManager, so runs 35115449692 and 35115452598 got the lock valid and debited with no credit on the destination. The validator init step now runs for every non-bitcoin leg without the input, and exports XDEX_SEED_LOCAL_VALIDATOR=1 for those legs only, because an identity alone signs against a cross_chain capability set that is empty on a fresh regtest. The bitcoin leg keeps its opt-in shape and local MINT gas path. (cherry picked from commit 124dc1f)
The harness discovers every rail's node and indexer credentials through the hub's getallconfigs, which a keyed hub gates behind HUB_API_KEY. A validator-mode host is keyed (validator init mints the key into the hub sidecar), and the hub, the indexer and the shared services all resolved it, but the e2e container was still deployed keyless: the litecoin and dogecoin matrix legs 401'd in initialCheck's beforeAll as "[chainRail] hub has no config for bitcoin/regtest" (run 35120852486) while the standalone bitcoin leg never noticed. Same resolution order as the indexer: host env first, then the sidecar, and a keyless host stays keyless. Three unit cases, the first two red without the passthrough.
…ks exist 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 the runner the hub boots before either coin stack, so the table is empty at that moment and the seeded validator never polled the BTC lock: run 35122297316 got the lock valid and debited with "CrossChainBridge: no indexer URL for chain BTC/LTC" and the credit never landed. The validator init step now exports the in-network indexer container names on port 3004 for the leg's coin and for bitcoin, which the hub passthrough carries, the same shape the rail's .env gives its hub. Unit case extended on both legs.
Pins the twelve tagged sibling components at their signed v0.19.0 master merge commits (xchain-contracts keeps v0.17.0) and arms the platform-train boundary: rule set 0.19.0, classification major, testnet BTC 152787 computed from tip 152716, mainnet at the sentinel, regtest genesis.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the package and lockfile to 0.19.0 and folds the Unreleased block into a dated 0.19.0 entry. The train carries the XBRIDGE cross-chain bridge, armed on testnet by the ladder and on mainnet at the sentinel, with the token bridge and policy code dark behind their sentinels, the rollcall and tracker fixes, the code-structure passes with the consensus identity pinned, and the activation registry.
Platform notes ride xchain-node's Release.