Release v0.10.0 - #23
Merged
Merged
Conversation
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.
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.
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.