Release v0.20.0 - #47
Merged
Merged
Conversation
…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.
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. (cherry picked from commit 973cc2c)
…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. (cherry picked from commit f4f0504)
…pinned to 1 on the nightly two-stack legs The hub's cross-chain engines propose a source leg only once it sits at the per-coin confirmation depth (coins/index.js resolveConfirmations), six blocks on BTC by default, and nothing on the nightly runner mines six BTC blocks behind a bridge lock: run 35124072478 held the litecoin leg's gas lock at "CrossChainBridge: not proposing BTC:3 (below depth 6)" until the suite gave up on the credit. The three names now ride the hub passthrough (hubPassthroughVars and the HUB_PASSTHROUGH_ENV view, which must mirror each other or the read throws), and the validator init step exports them at 1 for the litecoin and dogecoin legs, the way a regtest rail pins them. Inert on mainnet and testnet: the hub clamps a value below the per-coin default up to that default off regtest, so the passthrough can only raise the depth there.
# Conflicts: # .github/workflows/nightly-e2e.yml # test/unit/nightly_e2e_workflow.test/two_stack_legs.test.js
…ridge escrow proof on the two-stack legs
With the depth pinned to 1 the hub finalized the gas lock (run 35140173657:
"CrossChainBridge: finalized transfer ... BTC:3 -> DOGE 1 XCHAIN"), and the
next layer showed: before it credits a bridged transfer the destination indexer
fetches the escrow proof from the origin chain's indexer, resolved from its own
env as BTC_INDEXER_API_URL, and with nothing wired it holds the block at the
bridge proof barrier ("no origin-chain indexer endpoint is configured for the
escrow proof") for the 900 s ceiling while the chain ran 143 blocks ahead. The
ports step now writes that route into the coin's config file beside
BTC_SERVICE_HOST, which reaches every container of the coin, the indexer
included; the install already attaches the bitcoin indexer to the coin's docker
network, so its container name on the indexer's own port serves.
…nreleased after the v0.19.0 level Levelling develop with master (2d4b33b) placed the 0.19.0 section above the Unreleased line that was already on develop, which left that line under a second "### Fixed" heading inside 0.19.0 beside a repeat of the hub API key entry. The passthrough is not in v0.19.0; it and the escrow-proof route sit under Unreleased, and the duplicate block is gone.
…service moved into src/services/bootstrap_service/ The code-structure pass moved BootstrapService.js into src/services/bootstrap_service/archive_signing.js but kept the key path one level short, so DEFAULT_BOOTSTRAP_PUBKEY_PATH aimed at src/services/config/bootstrap_signing_pubkey.pem, which does not exist. loadBootstrapPublicKey() therefore returned null unless XCHAIN_NODE_BOOTSTRAP_PUBKEY was set: a signed-bootstrap restore refused as "unsigned" and the auto-bootstrap install path fell back to a full sync from genesis. The signing suite never caught it because every case writes its own key pair to a tmpdir and pins it through the env override, so the repo-pinned default was never exercised. Four cases now drive the default path with real fs and no override: the key ships where the module looks, it parses as an ed25519 public key, it is byte-identical to the file in the tree, and checkBootstrapSignature() consults it (a signature from a foreign key is refused for the right reason).
…r-node # Conflicts: # CHANGELOG.md
MIGRATE_CLI_PATHS only knew the two pre-v0.19.0 layouts, so the deploy precondition guard read null and refused with no scoped command against a container running the current indexer layout. The newest path is now checked first, and the two older ones stay for older still-supported builds. A pinned-list test fails if a future indexer move drops a path this guard needs again.
…sh empty dir The 'exits non-zero when the listing fails' test also proves the listing command succeeds against a healthy directory, using os.tmpdir() directly. A long-lived dev box's TMPDIR can accumulate tens of thousands of entries, so `ls -A` against it runs past mocha's 2000ms budget and times out for a reason that has nothing to do with the code under test. Use a directory this test creates and removes instead, which keeps both assertions (the listing fails on a nonexistent path, succeeds on a healthy one) intact.
bin/ci-full.sh hard-coded need_sib to xchain-hub alone, so a hand run missing xchain-indexer sailed past the gate and only broke later, confusingly, inside npm run ci. It now reads .ci-siblings (the same roster ci-reusable.yml's sibling-checkout step reads) so the two cannot drift, and arms XCHAIN_REQUIRE_SIBLINGS for the ci tier the same way ci-reusable.yml arms it once siblings are checked out. The coverage CI job re-ran the unit suite with no siblings checked out and no XCHAIN_REQUIRE_SIBLINGS, so every cross-repo guard silently skipped there: green by absence, not by the guard actually running. Mirrors ci-reusable.yml's own sibling-checkout step verbatim so coverage now measures the same suite the ci job runs, and only arms the flag when the checkout genuinely happened.
…hub and indexer containers Same shape as the rollcall gates lever: regtest only, absent on testnet and mainnet. Without it the admission-height crossing could not be armed on the rail.
This was referenced Sep 17, 2026
Pins eleven components at their signed, origin-verified v0.20.0 tags and carries xchain-contracts forward at v0.17.0, which this train does not touch. Arms the 0.20.0 rule set at BTC testnet 153116, computed from tip 153018. Generated by bin/write-release-manifest.js against origin, never by hand.
release: regenerate the v0.20.0 manifest from the signed sibling tags
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.
Title: Release v0.20.0
Summary
Added
XC_MIRROR_ADMISSION_ACTIVATIONpasses through to the hub and regtest indexer containers.Changed
Fixed
db/migrationCLI layout.Sibling release PRs
Merge order
Merge order: merge last, after all twelve sibling PRs are merged and tagged and the release manifest names those tags.
CI note
GitHub CI on this branch clones sibling repos at develop, not at this release branch, unless a workflow call passes siblings-ref for release branches. A cross-repo guard failure here can reflect that develop-only comparison rather than a real disagreement with a sibling still on this release branch; check the venue pre-push gate (which ships each companion at its own release branch) before treating a red guard as a defect.