Skip to content

Release v0.10.0 - #23

Merged
jdogresorg merged 18 commits into
masterfrom
release/v0.10.0
Aug 22, 2026
Merged

Release v0.10.0#23
jdogresorg merged 18 commits into
masterfrom
release/v0.10.0

Conversation

@jdogresorg

Copy link
Copy Markdown
Contributor

Cuts xchain-node for XChain Platform v0.10.0, last in the train.

The release manifest pins the twelve component commits this train actually tagged, read back from the published tags rather than from local state. Node cuts last because those merge-commit SHAs do not exist until the sibling PRs are merged and tagged, and every one of them is now green on master and tagged.

One correction rides along: the manifest now covers every module the installer clones. The 0.9.0 manifest listed eight of the twelve, so a pinned install of that train still resolved four modules at their default branch, which is exactly what a pinned install exists to prevent.

See CHANGELOG.md for what changed in this component.

The explorer reports healthy only once it holds a DB pool, and its pools
come from the coin stacks. Installing every service puts the explorer in
the shared bucket, which runs BEFORE any coin exists, so the readiness
check demanded a reply the explorer could not give: it answered 503
degraded for ten seconds and the whole stack install failed with
"Couldn't install the explorer module". A first install of the platform
on a clean host was unsatisfiable by construction.

It never surfaced on a dev box or a CI venue because both already carry
coin DBs from an earlier install, and the hosted e2e workflow had never
been driven past its clone step, so nothing had booted a genuinely empty
host since the check was written.

ExplorerConnector gains probe(), splitting the single boolean into the two
facts that differ here: a 503 means the server ANSWERED and holds no pool,
while a refused or timed-out connection means nothing answered. The
install now accepts answering-with-no-pools only while no coin is
installed, and keeps the full health check the moment one is, so a
genuinely broken explorer on a populated host still fails. ping() is
unchanged for its other callers.

Adds 10 tests (6 connector, 4 install). Full CI green (1518, 73, 58).
…ew coins

The explorer learns which coins exist by POLLING the hub, once a minute by
default. It is installed in the shared bucket, ahead of the coin stacks, so
after a fresh install it keeps answering 503 until a poll lands. Measured on
a clean host: the coin stack finished at 14:55:47 and a suite starting at
14:57:04 still got a degraded explorer.

The defect is that install returned success there. A caller that installs a
stack and then reads it inherits the race with nothing to warn it, which is
what broke the e2e gate: its suites start the instant install returns, and
its preflight ping got the 503.

installModules now converges the explorer before returning, and only when
the run actually installed a coin stack. It waits on an explorer that is
TALKING, since a degraded reply is a service mid-convergence; silence means
there is no explorer on this host to converge, and whether it came up at all
is installExplorerModule's question, already asked. Failing to converge
warns rather than fails: the stack is installed either way, and saying so is
better than pretending it is ready.

Adds 7 tests (4 on the wait, 3 on the install wiring). Full CI green
(1525, 73, 58).
…created

updateHub and updateExplorer push coin config to the hub and JOIN the hub
and explorer containers to each coin's docker network. They run in preCheck,
which fires BEFORE the action, so an install that creates brand-new coin
stacks finished without either shared service ever hearing about them.

The explorer is the visible casualty: installed in the shared bucket ahead
of the coin stacks, it ends up on no network from which the hub is
reachable, so it cannot read config, cannot populate a DB pool, and answers
503 forever. Measured on a clean host, it stayed degraded through a full
150-second readiness wait, which is what ruled out the poll-interval race
this was first taken for.

The new step runs from the install ACTION rather than inside installModules,
because it reconciles against live docker while installModules is also
driven directly by suites whose container registry is fixture data: run
there, the reconcile purged those rows and left the regression suite
asserting on an empty registry.

Nothing here fails the command. The modules are installed either way, and
the next command's preCheck runs the same two calls.

Adds 5 tests. Full CI green (1527, 73, 58).
A failure here carried off the runner only the e2e suite's log, which records
that a service answered wrong and never why. The service's own log and the
docker network topology are what hold the answer, and without them a remote
diagnosis is guesswork: the explorer refusing to serve on a clean runner cost
three wrong hypotheses, one fifteen-minute run at a time.

On failure only, dumps docker ps, the xchain networks and their members, each
container's network attachments, and the last 400 log lines per container into
the artifact that already uploads. Every command tolerates its own failure so
evidence-gathering can never mask the real error, and container inspection
takes network attachments rather than Config.Env, which carries credentials.
… runs

The e2e image stages its siblings from LIBRARY_BUNDLES and the suites reach
them at ../../../xchain-<name>. xchain-sync was required by tests but never
staged, and the consequence is worse than a failure: consensusHashConformance
catches the missing require and SKIPS, so it reported green while never
running.

That suite is the only place sync's BlockHasher meets the indexer's committed
ledger, actions and contract hashes over real stack data. The unit goldens in
each repo lock their own serialization; only this one recomputes every indexed
block through the full pipeline and compares the two implementations. Drift
between them is exactly what it exists to redden, and on a consensus release
it had been silently absent.

Two tests pin the bundle list, since a sibling dropped from it costs a skipped
guard rather than a red one.
install already waited for the explorer and warned when it never converged,
then exited 0 anyway, so the stack reported success while serving 503 to
every read. Callers hit the failure at their first query instead of here,
which puts the error a long way from its cause. The command now exits
non-zero; XCHAIN_NODE_ALLOW_DEGRADED_EXPLORER=1 keeps the old behaviour for
install-then-repair flows.
The explorer answering 503 with no DB pools is the failure this job keeps
hitting, and a container log alone cannot say whether the hub ever served it
coin config. Records the explorer ping, its hub-pointing env, and the key
structure of the hub config response. That response is in the hub sensitive
-read tier and carries DB credentials, so only key names are written and the
raw body is kept out of the uploaded directory.
400 lines covers a boot failure, which is what the limit was chosen for, but
a two-hour suite scrolls far past it. A defect in an early suite has already
been discarded by the time the job fails, so the one log that would explain
it arrives empty.

Hit while diagnosing an attestation execution that confirms on-chain and
never gets an execution row: the indexer's own account of that action was
gone, and the file's own note already said to raise this when early-run
evidence is needed.
The diagnostics step kept the last 400 lines of each container and ran only
on failure. Both discard evidence that cannot be collected again, because the
runner is destroyed with the job.

Any line cap is a bet on where the next defect lands, and this one lost: an
attestation execution that confirms on-chain and never gets an execution row
was undiagnosable off-runner, because the indexer's account of it had
scrolled away hours before the job failed. The whole suite log for a
two-hour run is under a megabyte, so keeping everything is affordable.

Capturing on success as well gives a healthy baseline to read a later
failure against, which is the comparison that was missing here. Artifacts are
per-run, so no run's evidence overwrites another's.
Fixing one defect cost a full two-hour pass, nearly all of it re-running
suites that already passed, and the cycle repeated for every fix. The runner
already accepts a suite name; this exposes it, so a fix costs a boot plus the
one suite it touches.

The security and performance suites stay skipped while a subset runs. They
grade the whole stack, and a partial action pass has not earned them.

Empty runs everything, which is what a release gate must do. A subset proves
a fix; only a full pass proves the train.
… to guess

The validator-onboarding suite STAKEs the hub's signing pubkey and asserts
the indexer then admits it to each capability set, so it has to know which
key the hub actually runs as. It read VALIDATOR_PUBKEY from the env and
skipped when unset, and the only way to set it was for an operator to
hand-copy the hex out of `validator status` into the coin config - so it
skipped everywhere nobody had, CI included, while reporting as a pending
test rather than as missing coverage.

Derive it from the same settings file the hub's own env is built from, so
the two can never name different keys. Public half only: the seed stays in
signing.key and goes to the hub alone. A standalone node has no validator,
so the var is absent and the suite still skips - correctly, because there
is no identity to onboard.
…onboard

The hosted stack's hub has never been a validator: p2pConfig is built only
when P2P_VALIDATOR_ADDR is set, that comes from the validator settings, and
initValidator is reachable from exactly one place - the explicit CLI
subcommand - which the install path the workflow runs never calls. So the
onboarding suite skipped on every hosted run for want of an identity.

Opt-in, default false, rather than always-on. Validator mode is not a
superset of standalone: peerManager exists, so startOracle() proceeds and
the hub begins finalizing its own price rounds, and several action suites
branch on precisely that (the fee fixtures size an output from a pair they
seed, and skip when the venue publishes its own XCHAIN/USD). Turning it on
for the whole matrix would quietly change what the gate measures, and a
single-suite probe of the onboarding test could not have shown it.

The step runs before install because ConfigService renders both the hub env
and the e2e container's VALIDATOR_PUBKEY at install time. It exports the two
vars a validator hub fails loud on and the standalone path never needs:
HUB_NETWORK, and ORACLE_MIN_SUBMISSIONS=1 because a lone validator can never
reach the default two-hub diversity floor, so no round would finalize and
every indexer's price-sync barrier would stall.
…n run

A BTC full action suite alone runs ~1h50m, and the security and performance
suites are sequenced after it, so at 120 minutes they shared whatever the
action suite left - in practice nothing. That is why they had never executed
once: the binding constraint was the job budget, not the tests, and no amount
of fixing the action suite could have reached them.

360 is the platform ceiling for a hosted job, so this imposes no limit the
runner would not anyway. Deliberate: those two suites have no measured
duration to size headroom against, and guessing low would re-create the same
invisible truncation one tier further along. The tradeoff is that a wedged run
takes the full six hours to report; the answer to that is a per-step timeout
on the suite steps, not a lower job budget that also caps a long green pass.
…it needs

The explorer's checkpoint, proof and cross-chain routes read hub-mirrored
tables from a local schema that xchain-sync deliberately never replicates.
Deployments with no externally-maintained hub schema colocated with the
explorer can now opt in (EXPLORER_CHECKPOINT_SELF_SYNC) to a checkpoint
descriptor the explorer's own mirror writer self-provisions, using the
indexer's DB identity so the two paths cannot drift. The indexer DB grant
covers the mirror schema on non-mainnet networks, and the Read Contract
flag passes through verbatim so the string-exact readers see it.
…ss config prompt

2026-08-20 xchain-platform review round sitting 3.
Findings: 5322, 5323, 5324, 5338, 5395.
Recorded in the platform review store and the round report.
…e lag

A status body with no lag field at all (an image that publishes none, or a
shed health POST falling back to GET /status) slipped past the lag refusal,
and a negative lag read as "ahead" when it means the service's committed tip
sits above its node's, so the rows it exports reference blocks the node no
longer recognizes. Both now refuse with a reason. The block-fetch desync
record is rendered field by field instead of as [object Object].
Pins the twelve tagged component commits, bumps the platform version and records
the train's changes.

The manifest now covers every module the installer clones. The 0.9.0 manifest
listed eight of the twelve, so a pinned install of that train still resolved
xchain-sdk, xchain-e2e-test, xchain-contracts and xchain-regtest-miner at their
default branch, which a pinned install is supposed to rule out.
@jdogresorg
jdogresorg merged commit ca16016 into master Aug 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant