Skip to content

chore: backport a month of maintenance from main - #2110

Merged
gilescope merged 37 commits into
release/node-2.1.0from
ozgb-backport-sept-maintenance
Sep 15, 2026
Merged

gilescope merged 37 commits into
release/node-2.1.0from
ozgb-backport-sept-maintenance

Conversation

@ozgb

@ozgb ozgb commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Overview

Follow-up to #2023, which brought release/node-2.1.0 in line with main up to
e029b0a9a. This picks up the 39 commits that have landed on main since
(e029b0a9a..24e296a2a): 7 are already on the branch as individual backports
(#1999, #2031, #2030, #2022, #2012, #2054, and the #2062 revert), 27 come
across and 5 do not.

Selection follows #2023's rule — a fix or maintenance change comes across, a
feature does not, with an exception for anything that makes the hardfork itself
testable — widened in three places by explicit request:

Why these belong on a mainnet release

The 5 exclusions

# why
#1996 feat: report BABE key presence in the keystore — BABE-family; #2023 excluded all 12 BABE commits and this branch has no BABE consensus.
#2085 Update version to v3.0.0 — would renumber this release.
#2028 Issue templates — GitHub only reads .github/ISSUE_TEMPLATE/ from the default branch.
#2072 .claude/skills/create-bug — no product effect.
#2097 Nothing to port: that was main checking out this branch's beta.1 metadata blob.

None carries exclusion risk.

Commits were applied in main's order, which satisfies every ordering
constraint on its own: #2047 needs #2021 first (both touch
tree-cache-guard/save), #2086 needs #2044 and #2048 first (attest pin, image
layers), and #1938 sits between #2059 and #2074 so the pre- and post-split
ledger/helpers paths resolve themselves.

Deviations from a straight cherry-pick

  1. refactor(ledger): give each ledger version its own wrapper #2059 leaked warp-sync code into ledger_9 — fixed in a follow-up commit.
    The refactor renames ledger/src/versions/common/** onto ledger_8/ but
    adds the ledger_9/ copy, so ledger_8 arrives with this branch's content
    while ledger_9 arrives with main's. That pulled serialize_ledger_snapshot
    and import_ledger_snapshot into ledger_9/mod.rs, both from Add ledger arena for warp sync #1650
    (warp-sync ledger arena) — one of the 16 commits chore: backport ledger 8->9 hardfork plus two months of maintenance #2023 excluded. They
    reference crate::SnapshotImportError, which Add ledger arena for warp sync #1650 defines in
    ledger/src/lib.rs and this branch does not have, so the workspace did not
    compile. Nothing calls either function here; removing them restores the
    parity refactor(ledger): give each ledger version its own wrapper #2059 is about (diff -r ledger/src/ledger_8 ledger/src/ledger_9 now
    differs only in version-binding aliases and docs). This is the one commit
    in the stack that is not a pick — please review it directly.
  2. fix(runtime): enter safe mode instead of freezing on failed multi-block migration #2079 — dropped the BABE test modules and the authority_keys
    scaffolding.
    Its runtime/src/lib.rs hunk carries the
    sidechain_slot_number / sidechain_status / slot_config test modules
    alongside failed_mbm_recovery; the first three need pallet_consensus_engine
    and were left out. runtime/src/migrations.rs is empty on this branch apart
    from its header, so the whole of main's file came into the conflict — only
    EnterSafeModeAndUnstuckOnFailedMigration was taken. The authority_keys
    scaffolding that came with it is explicitly dead ("nothing here is wired into
    SingleBlockMigrations"), is unrelated to this fix, and needs a
    pub mod authority_keys this branch's vendored partner-chains does not have.
    SafeMode takes pallet index 20, which is free here.
  3. feat(local-env): add non-validator archive node #2058 — kept configurations/readme.md deleted. That file does not exist
    on this branch: it was created by feat(local-environment): prepare for several BABE scenarios #1828, one of the excluded BABE commits, and
    documents AURA→BABE keystore scenarios. Only its midnight-node-6 paragraph
    was relevant, and that is already in local-environment/README.md, which took
    the incoming version.
  4. Generated metadata/static/*.scale conflicts resolved to ours — see the
    TODO below. midnight_metadata_3.0.0.scale is deliberately not created
    here; it belongs to Update version to v3.0.0 #2085, which is excluded.

Routine conflict classes were resolved by #2023's standing rules: the local-env
contract-compiler Dockerfile kept ours, since this branch deliberately unpins the
apt versions upstream keeps bumping.

🗹 TODO before merging

📌 Submission Checklist

  • All commits are signed off (git commit -s) for the DCO
  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason:
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • Updated AGENTS.md if build commands, architecture, or workflows changed
  • No new todos introduced

🧪 Testing Evidence

Run locally against the full 28-commit stack:

  • cargo check --workspace --all-targets — clean
  • cargo fmt --all --check — clean
  • cargo clippy --workspace --all-targets — clean

The first cargo check is what caught deviation (1): it failed with
unresolved import crate::SnapshotImportError in ledger/src/ledger_9/mod.rs,
which is how the #1650 leak was found rather than shipped.

Not run locally, left to CI: hardfork_e2e (needs a branch-built NODE_IMAGE;
with it unset the test resolves to the registry's latest-main and exercises
main, not this branch) and the Local Environment Tests.

  • Additional tests are provided (if possible)

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other:
  • N/A

Two runtime-visible items:

Both need the metadata rebuild noted above.

#2096 is a consensus-relevant ledger bump. 8.1.2 accepts strictly less than
8.1.1, so it must be deployed before any peer relies on the stricter decoding.

Links

Assisted-by: Claude:claude-opus-5

gilescope and others added 28 commits September 8, 2026 12:12
)

Signed-off-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* security: bump npm to version with stronger defaults and be explicit about version number.

Signed-off-by: Giles Cope <gilescope@gmail.com>

* fix: push file down to where used

Signed-off-by: Giles Cope <gilescope@gmail.com>

* ci: pin to explicit node version and bump major

Signed-off-by: Giles Cope <gilescope@gmail.com>

* feat: versions in global arg

Signed-off-by: Giles Cope <gilescope@gmail.com>

---------

Signed-off-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…#2044)

* chore(deps): bump actions/attest-build-provenance from 4.1.1 to 4.2.2

Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 4.1.1 to 4.2.2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](actions/attest-build-provenance@0f67c3f...4d10147)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 4.2.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* rename action

Co-authored-by: Squirrel <giles.cope@shielded.io>
Signed-off-by: Squirrel <giles.cope@shielded.io>

* fix: update sha also

Signed-off-by: Giles Cope <gilescope@gmail.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Squirrel <giles.cope@shielded.io>
Signed-off-by: Giles Cope <gilescope@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Squirrel <giles.cope@shielded.io>
Co-authored-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.8 to 2.85.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@cb33e69...7f4eb89)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.9.1 to 2.9.2.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](Swatinem/rust-cache@c193711...6323deb)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-version: 2.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
)

* chore: stop duplicating binaries and res/ in a second image layer

The node and toolkit images copied files as root, then ran `chown -R` over the
same paths. chown rewrites every file, so buildkit stored a second full copy in
the chown layer -- 210MB on the node image (a 133M binary plus the 74M res/
tree), and the toolkit-js tree on the toolkit image.

Copy with `COPY --chown=appuser:appuser` instead (appuser moved above the COPYs
in both base Dockerfiles) and leave `chown` covering only the directories that
`RUN mkdir` creates.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add PR link to image layer dedup change file

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* chore(deps): land the safe subset of the pending major bumps

Clears four of the nine major updates queued on the Renovate dependency
dashboard (#1090) in one PR:

- actions/cache/save v4 -> v6.1.0 (ESM migration only; inputs unchanged)
- azure/setup-kubectl v4.0.1 -> v5.1.0 (node24 runtime, dl.k8s.io)
- contract-compiler base node:22-slim -> node:24-slim; @types/node 22 -> 24
  in util/toolkit-js, which already targets @tsconfig/node24. Still Debian
  bookworm, so the pinned apt package set is unaffected.
- paritytech/srtool: track the whole `<rust>-<srtool>` tag in one ARG. The
  old split ARG made renovate read `0.18.4` as the image version and offer
  `1.93.0` as a "v1 major", which resolves to a tag that does not exist.

The remaining five are left alone on purpose - see the PR body.

Assisted-by: Claude:claude-opus-5 claude-code
Signed-off-by: Giles Cope <gilescope@gmail.com>

* fix: outstanding issues

Signed-off-by: Giles Cope <gilescope@gmail.com>

* chore(deps): take docker/compose v5 with buildx; revert typescript 7

compose v5.0.0 removed its internal buildkit builder and delegates `build:` to
Docker Bake, so the buildx plugin is now installed alongside the compose plugin
in the CI base image - earthly injects a bare docker CLI with no bundled
plugins, and local-env's compose file has a `build:` service. Verified by
building the contract-compiler image with compose v5.5.0 + buildx v0.36.1
through the bake path.

TypeScript 7 comes back out. It passed every build-time check in
util/toolkit-js - tsc, all four compact variants - and then failed at runtime in
CI, taking out +rebuild-genesis-state and all four Toolkit E2E jobs:

    Error loading configuration '/toolkit-js/test/contract/contract.config.ts'
    .. Unexpected error while compiling TypeScript configuration
       Cannot read properties of undefined (reading 'fileExists')

@midnight-ntwrk/compact-js-command declares ts-node ^10.9.2 and its
ConfigCompiler uses it to compile contract.config.ts at run time; ts-node drives
the JavaScript compiler API that the native port does not expose. So ts-node was
the blocker after all - reaching in transitively through one of our own
packages, which is why it was invisible in package.json and to tsc. The
`types: ["node"]` tsconfig line that TS 7 needed goes with it.

Also adds the change file for the whole backlog sweep.

Assisted-by: Claude:claude-opus-5 claude-code
Signed-off-by: Giles Cope <gilescope@gmail.com>

* Update renovate-major-backlog.md

Signed-off-by: Squirrel <giles.cope@shielded.io>

---------

Signed-off-by: Giles Cope <gilescope@gmail.com>
Signed-off-by: Squirrel <giles.cope@shielded.io>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* feat(local-env): add non-validator archive node

Signed-off-by: Tomasz Bartos <tomasz.bartos@shielded.io>

* chore: mark 55a7680 as AI-assisted

The archive-node commit was drafted with AI assistance; adding the
marker required by AGENTS.md as a follow-up rather than amending, per
the repository's no-force-push rule.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Tomasz Bartos <tomasz.bartos@shielded.io>

* docs(local-env): document the non-validator archive node in topology guides

The startup-phases table in local-environment/README.md and the node-keys
guide in configurations/readme.md still described a five-validator stack.
Document midnight-node-6: non-validator archive follower, its published
ports (30338 p2p / 9945 RPC / 9620 Prometheus), its persistent volume, and
that it carries no keys and is skipped by validator-discovering tooling.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Tomasz Bartos <tomasz.bartos@shielded.io>

* chore: move archive-node change file into the node component

The release archive/reset workflow only handles changes/node,
changes/toolkit, and changes/runtime; a file left in the legacy top-level
changes/added/ is never archived or reset and would be re-emitted in
subsequent release notes.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Tomasz Bartos <tomasz.bartos@shielded.io>

---------

Signed-off-by: Tomasz Bartos <tomasz.bartos@shielded.io>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* fix: toolkit update-ledger-parameters erroneous param re-use

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* docs: add pr link to change file

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@bb05f3f...37fe631)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [midnightntwrk/upload-sarif-github-action](https://github.com/midnightntwrk/upload-sarif-github-action) from b747f959d898a4ffdcc2cea4e2daafc61a0c33e9 to 362d0346b194663004cb371e8e8523bd1c910917.
- [Release notes](https://github.com/midnightntwrk/upload-sarif-github-action/releases)
- [Changelog](https://github.com/midnightntwrk/upload-sarif-github-action/blob/main/CHANGELOG.md)
- [Commits](midnightntwrk/upload-sarif-github-action@b747f95...362d034)

---
updated-dependencies:
- dependency-name: midnightntwrk/upload-sarif-github-action
  dependency-version: 362d0346b194663004cb371e8e8523bd1c910917
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…the next block instead (#1980)

* fix: c2m-bridge hardening: do not skip failed transfers and retry in the next block instead

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

* Simplify handle_transfers

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

* Be specific that the only situation of partially processed Cardano transaction is Reserve part has been processed by the ICS part has not

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

* Update changes files

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

* Update TransferHandlerError docs with SCALE explanation

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

* Fix comments

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>

---------

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.11 to 2.86.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@7f4eb89...5b4d68e)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.86.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…bust (#2032)

* fix(local-env): make qanet from-genesis and fork-network full mode robust

Two maintenance fixes to local-environment tooling flagged in review of #2023:

- qanet.network.yaml: uncomment the SEED_PHRASE references (node1-12) so
  `run qanet --from-genesis` discovers its validators. qanet was the only
  well-known network with these commented out, so from-genesis always threw
  "none of the seed env vars are set ()" even when seeds were provided. The
  node still reads keys only from the generated *_SEED_FILE mounts, so
  snapshot/fork bring-up is unaffected.

- fork-network.yml: drive `full` upgrade mode as its two phases (image-upgrade
  then governance-runtime-upgrade --skip-run) with the runtime step's RPC
  endpoint probe in between, instead of the all-in-one full-upgrade command
  that hardcodes ws://127.0.0.1:9950. This makes full mode resolve a reachable
  endpoint (published port or node1's docker bridge IP) on self-hosted runners
  where the loopback -> published-port path is black-holed, completing the
  follow-up noted in the runtime-mode fix.

Assisted-by: Claude:claude-4.8-opus
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* chore: add PR link to local-env fix change files

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

---------

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* Give local-env to downstream consumers

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* changes file, tests

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* pr comments

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* chore: add local environment change tag

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* fix: preserve manifest image on partial rollouts

Only refresh the fork manifest image when the primary validator is rolled, preventing node-specific canaries from advertising an incompatible image to downstream consumers.

Assisted-by: Codex:GPT-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* chore: remove unrelated warp change file

Assisted-by: Codex:GPT-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* fix: harden fork manifest generation

Parse OCI image tags without confusing registry ports or digests, validate manifest namespaces, and document local-only WebSocket endpoints for security scanning.

Assisted-by: Codex:GPT-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* fix: place scanner suppressions inline

OpenGrep applies nosemgrep annotations only to findings on the same source line.

Assisted-by: Codex:GPT-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

* chore: remove ineffective scanner annotations

The repository scan uploads all OpenGrep results and does not honor inline annotations. Track the reviewed local-only cases through GitHub code-scanning dismissals instead.

Assisted-by: Codex:GPT-5
Signed-off-by: Justin Frevert <justinfrevert@gmail.com>

---------

Signed-off-by: Justin Frevert <justinfrevert@gmail.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.86.3 to 2.86.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@5b4d68e...b6ff580)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.86.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* ci: derive image publish targets from the repository

CI's registry, image name and OCI source label were hardcoded to
midnightntwrk's namespaces. This repo gets cloned into private forks,
and those clones inherit org runners and secrets, so their CI starts and
publishes -- into midnightntwrk's registries, including the public
ghcr.io/midnightntwrk mirror and the shared latest-{amd64,arm64} tags.

Take the target from the repo instead:

- Earthfile gains four global ARGs next to CACHE_KEY/CI --
  GHCR_REGISTRY, GHCR_REGISTRY_PUBLIC, IMAGE_REPO and IMAGE_SOURCE_URL --
  replacing the per-target `ENV GHCR_REGISTRY=...` lines. GHCR_REGISTRY
  is read nowhere at runtime, only inside the Earthfile, so nothing is
  lost by dropping the ENVs.
- GHCR_REGISTRY_PUBLIC defaults to GHCR_REGISTRY and the mirror push sits
  behind `IF [ "$GHCR_REGISTRY_PUBLIC" != "$GHCR_REGISTRY" ]`, so only a
  repo that deliberately sets a different public namespace can publish
  publicly. (A duplicate tag in one SAVE IMAGE list is a hard error in
  Earthly 0.8, hence the IF rather than a self-cancelling duplicate.)
- .envrc forwards all four to earthly via EARTHLY_BUILD_ARGS, appended
  the same way CI=true already is, so every invocation that sources it
  picks them up and no workflow has to repeat the derivation. Local runs
  have no GITHUB_REPOSITORY and keep the ARG defaults.
- Workflows derive GHCR_REGISTRY at workflow-level env and expose
  node_image / toolkit_image (+ _public) as job outputs, so the ~20
  downstream image references shrink to `<image>:<tag>`. The composite
  actions already take node-image/toolkit-image inputs and are unchanged.
- Publishing outside the current repo's namespace is gated on being the
  canonical repo rather than retargeted: main.yml's two root publish jobs
  (every other job needs them), the Docker Hub push in
  build-publish-contract-precompiles.yml, and release-image.yml, which
  refuses rather than skips because its downstream jobs are guarded with
  `!cancelled() && !failure()` and would run through a skipped dependency.

Also two repo-agnostic CI fixes found alongside:

- A failed Docker Hub login no longer fails the build. It is pull-side
  rate-limit relief for docker.io base images only, but the
  `if [ -n "$DOCKERHUB_TOKEN" ]` guard made a bad credential fatal to
  +test, +test-toolkit, the local-env targets and the SBOM scan jobs.
- changes_check.yml's GHCR login used `registry: ghcr.io/midnight-ntwrk`,
  a path-qualified value docker/login-action never accepted; it wants a
  registry host.

Behaviour for midnightntwrk/midnight-node is unchanged: rendering every
new reference with the canonical values reproduces the previous file
byte-for-byte. The one intentional exception is the OCI source label on
the benchmarks and toolkit images, which pointed at
midnight-ntwrk/artifacts while the CI image pointed at
midnightntwrk/midnight-node; all three now share IMAGE_SOURCE_URL.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* ci: publish from any repo, not just the canonical one

The previous commit closed the leak into midnightntwrk's registries by
gating main.yml and release-image.yml on being the canonical repo:
`if: github.repository == 'midnightntwrk/midnight-node'` on main.yml's
two root publish jobs, and a "Refuse to publish from a fork" `exit 1` in
release-image.yml's validate-inputs. Right for the leak, but too blunt:
a private clone merging to main gets no images at all and cannot cut a
release.

Image names already derive from
${GHCR_REGISTRY}/${GITHUB_REPOSITORY#*/}, so two clones of midnight-node
publish to distinct GHCR images and each repo's artifacts can be
access-controlled independently. Only these two workflows opted out.

So drop both gates and apply the derivation the rest of CI already uses:

- Both files get the workflow-level GHCR_REGISTRY / GHCR_REGISTRY_PUBLIC
  env block from continuous-integration.yml, and expose node_image /
  toolkit_image as job outputs (publish-amd64, prepare-release) since
  the `env` context is not available in a reusable workflow's `with:`.
- Every reference to the private ghcr.io/midnight-ntwrk namespace becomes
  ${GHCR_REGISTRY}/${IMAGE_REPO}. The image basename follows the repo
  name too, so a fork publishes <owner>/<its-repo> rather than a
  same-named image in its own namespace.
- The Earthly `Run build` steps need no change: they source .envrc, which
  already forwards GHCR_REGISTRY/IMAGE_REPO as build args. What was
  hardcoded is the hand-written docker/gh plumbing around them.

Three things stay gated on the canonical repo, because they are things a
fork genuinely cannot do rather than things it should do differently:

- The public ghcr.io/midnightntwrk mirror. On a fork GHCR_REGISTRY_PUBLIC
  equals GHCR_REGISTRY, so the mirror steps would collapse onto refs the
  build already pushed -- re-creating identical indexes and attesting one
  digest twice under two subject names.
- Docker Hub. `midnightntwrk` there is a Docker Hub org login, not a
  GitHub owner, so there is no derivable equivalent and a fork has
  neither the namespace nor the secrets.
- Every actions/attest step, including the private-namespace ones.
  actions/attest requires GitHub Enterprise Cloud for a private or
  internal repo, so on a fork they hard-fail; and with the default
  push-to-registry: false they would write attestations bearing
  upstream's subject-name into the fork's own store. This also covers
  srtool-build.yml's three attest steps, which only checked
  `release-tag != ''` and would otherwise start firing on a fork now that
  release-image.yml can call them.

Semantically the mirror group could test GHCR_REGISTRY_PUBLIC !=
GHCR_REGISTRY instead, mirroring the Earthfile, but the `env` context is
not available in a job-level `if:`, so the mirror-only SBOM jobs cannot
express it. One predicate everywhere is simpler and works uniformly.

Net effect on a fork: it builds and pushes to its own GHCR namespace,
creates its own draft/release, and silently skips mirror, Hub and
attestations.

Behaviour for midnightntwrk/midnight-node is unchanged. Rendering every
reference in the three files with the canonical values reproduces HEAD's
set of image references exactly, and every added `if:` is the repo check
ANDed onto the condition that was already there, so all of them evaluate
true upstream.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(ci): don't fail main.yml on a Docker Hub login failure

Two fixes the canonical-repo gates were masking, both now reachable
because main.yml and release-image.yml run from any repo.

main.yml's two `Login to DockerHub` steps have no continue-on-error and
a fork has no Docker Hub credentials, so they hard-fail before anything
else in the job runs. The login is pull-side rate-limit relief for
docker.io base images only -- the same bug and the same fix as the
Earthfile and sbom-scan-image.yml logins in the previous commit.
release-image.yml's prepare-release login has the same shape and gets
the same treatment.

Season's SEASON_VAR_NODE_DOCKER_IMAGE / _TOOLKIT_DOCKER_IMAGE were bare
Docker Hub strings baked into the release-notes body. Harmless to CI,
but a fork's own release notes would tell readers to pull upstream's
images. Point them at ${GHCR_REGISTRY_PUBLIC}, the publicly pullable
equivalent, which on a fork resolves to that fork's namespace.

The Season change does alter the canonical repo's release notes: they
now name ghcr.io/midnightntwrk/midnight-node{,-toolkit} instead of the
Docker Hub image. Both are published for every release, so the notes
stay accurate.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(ci): pass image refs into run blocks via env, not template expansion

zizmor flagged 14 "code injection via template expansion" findings on
continuous-integration.yml, all of them `${{ }}` expanded directly into
a `run:` block -- the pattern AGENTS.md already tells us not to use.

Seven of the expansions predate this branch (the `*_image_tag` halves).
The other seven are the `*_image` halves added when image names moved
from hardcoded literals to job outputs, which also rewrote the lines and
so brought the whole set into this PR's diff.

The exposure is real rather than theoretical. Both halves derive from
`node/Cargo.toml`'s version field, read with

    grep -m 1 'version =' node/Cargo.toml | awk '{print $3}' | tr -d '"'

and this workflow runs on `pull_request`, so a fork controls that file.
A version string containing shell metacharacters lands inside a command
substitution on a self-hosted runner. Passing the value through `env:`
puts it in the process environment instead of the script text, so it can
no longer be parsed as shell.

Converted four step blocks -- the two `Check for existing images` steps,
`Check Genesis Rebuild` and `Run metadata check` -- plus one more the
scanner did not report, the indexer job's CONTENT_HASH, which this branch
introduced in the same style. No `${{ }}` expansion remains inside any
run block in this file.

Values are unchanged: each expression moves verbatim into `env:` and the
script references it as a shell variable, so the resulting image refs and
build args are identical.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* ci: namespace the CI base image by repository too

The CI base image was left with a literal `midnight-node-ci` name while
every other image moved to `$IMAGE_REPO`. GHCR_REGISTRY only isolates by
*owner*, so two clones under one owner write the same
`$GHCR_REGISTRY/midnight-node-ci:$RUST_VERSION-$COMPACTC_VERSION-$ARCH`
and the later run silently replaces the earlier image.

Scope, to be accurate about it: nothing builds *from* that reference.
`prep-no-copy` FROMs `midnightntwrk/midnight-node-ci` on Docker Hub, so
the GHCR copy is a staging artifact that only the canonical repo goes on
to mirror. The collision therefore corrupts a staging image rather than
anyone's build inputs today. It stops being harmless the moment either
the FROM is pointed at GHCR or a clone uncomments the local-build path
just above it, so name it correctly now rather than rely on that.

IMAGE_REPO defaults to midnight-node, so the canonical reference stays
`midnight-node-ci` byte-for-byte; only the workflow's pull side needed
updating to match, and the Docker Hub tags stay literal inside the branch
that only the canonical repo takes.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(ci): don't carry the public image name in a job output

`push-combined-manifest` failed with "invalid reference format": the
`IMAGE` env it builds the manifest from arrived empty, so the command ran
as `imagetools create -t ":2.1.0-<hash>"`.

The `run` job's log gives the reason:

    ##[warning]Skip output 'node_image_public' since it may contain secret.
    ##[warning]Skip output 'toolkit_image_public' since it may contain secret.

Actions refuses to propagate a job output whose value contains a
registered secret. DOCKERHUB_MIDNIGHTNTWRK_USER is the literal string
"midnightntwrk", which is a substring of
ghcr.io/midnightntwrk/midnight-node, so both public outputs were dropped
and resolved to "".

This is a regression from this branch. main had `IMAGE:` as a hardcoded
literal, which never travelled through the outputs machinery; deriving it
put it there for the first time. The private name survives only because
midnight-ntwrk is hyphenated and so does not contain the secret -- which
is why `node_image` works and `node_image_public` does not.

Recompute the public name in the two steps that consume it, from the
workflow-level GHCR_REGISTRY_PUBLIC and $GITHUB_REPOSITORY, and drop the
two job outputs plus the step outputs feeding them. Nothing else read
them. Renders to the same ghcr.io/midnightntwrk/midnight-node{,-toolkit}
the literals used, and a fork still gets its own namespace.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* ci: add '{}' for readability

Co-authored-by: Squirrel <giles.cope@shielded.io>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Co-authored-by: Squirrel <giles.cope@shielded.io>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…2025)

* chore(deps): route dependabot/renovate PRs to canonical bot:* labels

Dependabot's default labels (dependencies, github_actions, ...) diverge
from the org label standard; label each ecosystem bot:dependencies /
bot:github-actions.

Assisted-by: Claude <noreply@anthropic.com>

* fix(ci): exempt the canonical bot labels from changes checks

changes_check matches labels with contains() over a fromJSON array, which is an
exact element match, so 'dependencies' does not match 'bot:dependencies'.
Routing Dependabot to the canonical labels would therefore have dropped the
exemption and failed the link check on bot PRs touching non-ignored files.

The legacy names stay listed for now: both label sets currently exist on the
repo, so in-flight bot PRs still carry the old ones.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chris Ferry <chris.ferry@shielded.io>

* docs(changes): add change file for PR 2025

Required by the changes check, which wants both a PR link and an issue link.

DCO Remediation Commit for Chris Ferry <chris.ferry@shielded.io>

I, Chris Ferry <chris.ferry@shielded.io>, hereby add my Signed-off-by to this commit: 2a8e13b

Signed-off-by: Chris Ferry <chris.ferry@shielded.io>

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: Chris Ferry <chris.ferry@shielded.io>
Co-authored-by: justinfrevert <81839854+justinfrevert@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* refactor(ledger): give each ledger generation its own copy of the wrapper

`ledger/src/versions/common/**` was compiled twice through a
`#[path = "versions"]` module-parameterization trick — once bound to the
ledger-8 crates, once to the ledger-9 crates — with `super::` resolving
differently in each instantiation. A reader of a file under `versions/common/`
could not tell which ledger crate `mn_ledger_local` referred to, and an edit
meant for one generation silently applied to both.

Each generation now owns its files under `ledger/src/ledger_8/` and
`ledger/src/ledger_9/`, with absolute `crate::ledger_N::…` imports so every
file states the generation it binds. `ledger/src/common/` — the SCALE types
crossing the runtime/client interface, version-independent by design and
compiled once — becomes `ledger/src/boundary/`, so no folder is called
`common` any more. All `#[path]` and `#[allow(clippy::duplicate_mod)]`
scaffolding is gone from `lib.rs`.

No behaviour change: rustc already emitted both instantiations, so this only
makes the existing duplication visible in the source tree. There was no
non-test version branching in the shared half to reinterpret, public paths
(`ledger_8::…`, `ledger_9::…`, `latest::…`, `types::…`, `host_api::…`) are
unchanged, and the only edits outside `ledger/src/{lib.rs,ledger_8,ledger_9}`
are two `common::` -> `boundary::` import lines in `host_api/`.

Normalising the version path away, `diff -r src/ledger_8 src/ledger_9` leaves
exactly five differing files — `error_ext.rs`, `guaranteed_validation.rs`,
`post_block_update.rs`, `system_tx.rs`, and `mod.rs`'s crate aliases,
`CRATE_NAME` and `TransactionSignature`. Everything else is identical, which
is the check this layout buys.

Issue: #1768

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add PR link to ledger split change file

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata for the renamed ledger module paths

`scale-info`'s `#[derive(TypeInfo)]` records `module_path!()` in each type's
`Path`, so the runtime metadata embeds Rust module paths verbatim. Moving the
ledger wrapper into per-generation directories therefore changes the metadata's
type registry even though nothing about the SCALE encoding moved, and
`+check-metadata` byte-diffs the blob.

Decoding every `midnight_node_ledger`-rooted path out of the old and new blobs
gives 18 renames, 18 unchanged type names, and nothing added or removed:

  common::types::{Op, Tx, UtxoInfo}
    -> boundary::types::{Op, Tx, UtxoInfo}

  ledger_9::common::types::{DeserializationError, DisjointCheckErrorCode,
    EffectsCheckErrorCode, FeeCalculationErrorCode, InvalidError, LedgerApiError,
    MalformedContractDeployErrorCode, MalformedError, MalformedZswapErrorCode,
    SequencingCheckErrorCode, SerializationError, SystemTransactionError,
    TransactionApplicationErrorCode, TransactionError, ZswapInvalidErrorCode}
    -> ledger_9::types::{...}

The whole blob shrinks by exactly 99 bytes (148858 -> 148759), which is the
arithmetic of those renames and nothing else: 15 paths drop the `common`
segment (-7 bytes each, one compact length prefix plus six characters) and 3
grow from `common` to `boundary` (+2 bytes each).

No variant, discriminant, field, or field order changed -- `boundary/types.rs`
is a 100% rename of `common/types.rs`, and `ledger_9/types.rs` differs from the
`versions/common/types.rs` it came from by a single `use` line. Anything already
encoded on chain still decodes identically; only the names a fresh subxt codegen
emits are different.

`midnight_metadata_2.1.0.scale` is regenerated alongside `midnight_metadata.scale`
because the two are the same blob (that file tracks main rather than the release
it names).

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* docs: generation -> version

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
… cache on mainnet (#1938)

* perf(toolkit): faster resilient chain sync and replay, working wallet cache on mainnet

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

* chore: add PR link to change file

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

* fix(toolkit): gate relaxed replay verification on state-root presence

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

* fix(toolkit): verify genesis transactions fully during replay

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

* fix(toolkit): skip balancing re-check during replay

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

* fix(toolkit): trim comments, bound tip re-check, restore cache at any height

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>

---------

Signed-off-by: Jacek Kurkowski <jacek.kurkowski@shielded.io>
Co-authored-by: Fenton Haslam <50406962+Fentonhaslam@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…e helpers (#2074)

* chore(ledger-helpers): drop unreferenced files

`versions/block_context/pre_ledger_8.rs` is dead: nothing has declared it as a
module since ledger 8 became the oldest supported generation. The two
`fork/contract_operation_*.rs` files are empty (0 bytes) and equally unreferenced.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* refactor(ledger-helpers): give each ledger version its own copy of the helpers

`ledger/helpers/src/versions/common/**` was compiled twice via a
`#[path = "versions"]` module-parameterization trick, once bound to the ledger-8
crates and once to the ledger-9 crates, with `super::` resolving differently in
each instantiation. A reader of a file under `versions/common/` could not tell
which ledger crate `mn_ledger` referred to, and an edit meant for one version
silently applied to both — the same footgun #2059 removed one layer down.

Each version now has its own directory, `ledger_8/` and `ledger_9/`, with
absolute `crate::ledger_N::…` imports so every file states the version it binds.
The two inline modules in `lib.rs` become the respective `mod.rs` files, and the
version-specific single files are renamed to line up across the two trees:

  versions/block_context/post_ledger_8.rs -> ledger_N/block_context.rs
  versions/ecdsa_unimpl.rs                -> ledger_8/ecdsa.rs
  versions/test_utilities_compat.rs       -> ledger_8/test_utilities_local.rs
  versions/ecdsa_wallet_tests.rs          -> ledger_9/ecdsa_wallet_tests.rs

`ledger_9/ecdsa.rs` and `ledger_9/test_utilities_local.rs` are new one-line
re-exports of what `lib.rs` used to alias inline, so the two directories hold
the same filenames and `diff -r` is meaningful.

No behaviour change: rustc already emitted both instantiations. Public paths are
unchanged (`pub use common::*;` already flattened `common` away), so no consumer
needed an edit and the runtime metadata is untouched. The same 150 unit tests
and 2 doctests run, under the same names minus the `common::` segment.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add PR link to ledger-helpers change file

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(ledger-helpers): carry the relaxed-replay changes into the ledger 9 context

`perf(toolkit): faster resilient chain sync and replay` (#1938) landed on
main after this branch copied `versions/common/context.rs`. The rebase
applied it to `ledger_8/context.rs` (the rename target) but the `ledger_9`
copy is a fresh add, so it silently kept the old code: no `state_root()`,
no `strictness_for`/`update_from_tx_with_strictness`, no
`StateRootUnavailable` fail-closed root check and no `replay_stats`
counters. Since the crate root re-exports `ledger_9` as the latest
version, that is the copy every caller actually gets.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…ers (#2075)

* refactor(toolkit): give each ledger version its own copy of the builders

`tx_generator/builder/builders/common/**` and `commands/fork/common/**` were
each compiled twice via a `#[path = "common"] pub mod inner { … }` trick, once
bound to the ledger-8 helpers and once to the ledger-9 helpers, with
`ledger_helpers_local` resolving differently in each instantiation. A reader of
a file under `common/` could not tell which ledger version it was looking at,
and an edit meant for one version silently applied to both. This finishes the
job #2059 and the ledger-helpers split started.

Each version now has its own directory — `builders/{ledger_8,ledger_9}/` and
`commands/fork/{ledger_8,ledger_9}/` — and each file names its own version with
a `use midnight_node_ledger_helpers::ledger_N as ledger_helpers_local;` line, so
the two copies stay byte-identical apart from that one word and `diff -r` can
police them. The only genuine divergence left is `builders/*/mod.rs`, which
carries the per-version `serialize_tx`.

The `inner` wrapper module is gone (nothing referenced it), so every external
path — `builders::ledger_8::SingleTxBuilder`, `fork::ledger_9::show_wallet`, … —
is unchanged, as is the CLI surface.
`impl_encoded_zswap_conversions!` stays in `builders/mod.rs`: `ledger_storage`
still aliases to `ledger_storage_ledger_8` in both versions, so duplicating the
impls per version would still hit E0119.

With this, `grep -r '#\[path' --include='*.rs'` over the workspace returns
nothing, and no directory is compiled more than once.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add PR link to toolkit change file

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.86.7 to 2.87.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@b6ff580...37f7c57)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.87.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Bumps [anchore/sbom-action/download-syft](https://github.com/anchore/sbom-action) from 0.24.0 to 0.24.1.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](anchore/sbom-action@e22c389...aa80c8c)

---
updated-dependencies:
- dependency-name: anchore/sbom-action/download-syft
  dependency-version: 0.24.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
* fix: bump ledger 8 to 8.1.2 and ledger 9 to 9.1.0.0-rc.5

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add pr link to change files

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* Update changes/node/changed/bump-ledger-9.1.0.0-rc.5.md

Co-authored-by: Lech Głowiak <LGLO@users.noreply.github.com>
Signed-off-by: Squirrel <giles.cope@shielded.io>

* Apply suggestion from @gilescope

Signed-off-by: Squirrel <giles.cope@shielded.io>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Signed-off-by: Squirrel <giles.cope@shielded.io>
Co-authored-by: Squirrel <giles.cope@shielded.io>
Co-authored-by: Lech Głowiak <LGLO@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…ck migration (#2079)

* fix(runtime): enter safe mode instead of freezing on failed multi-block migration

A failed MBM previously left the migration cursor Stuck forever: only
inherents were admitted into blocks and can_set_code rejected runtime
upgrades - a permanent liveness failure with no on-chain recovery on a
standalone chain. Upstream's EnterSafeModeOnFailedMigration returns
KeepStuck and does not help (paritytech/polkadot-sdk#12921).

Add pallet-safe-mode at the reserved index 20 and a custom
FailedMigrationHandler that enters safe mode indefinitely and returns
ForceUnstuck (falling back to freezing if entry fails). While in safe
mode, BaseCallFilter admits only the inherents (required for block
validity) and the existing governance allowlist, so governance can ship
a fixed runtime and force_exit safe mode. Zero behavior change until a
migration actually fails.

CurrencyWaiver's hold Reason is retargeted from pallet_session's
HoldReason to the composite RuntimeHoldReason to satisfy safe-mode's
Currency bound; deposits are disabled, so it stays typecheck-only.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix(runtime): whitelist federated motion calls in safe mode

The collectives dispatch an approved proposal with their (non-Root)
Members origin, which goes through BaseCallFilter. Without
FederatedAuthority::motion_approve in the safe-mode whitelist neither
body could record an approval, so no motion could ever be closed to
dispatch a fixed runtime as Root - the recovery flow this change exists
to provide. Whitelist motion_approve and motion_revoke; both are
origin-gated to the collective proportion origins, so this adds no
exposure for signed or unsigned traffic.

Add an end-to-end regression test driving the full recovery flow while
in post-failure safe mode: propose/vote/close in both collectives,
motion_close dispatching System::authorize_upgrade as Root, then
force_exit. The test fails without the whitelist fix.

Addresses PR review feedback.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…4.1 (#2089)

Bumps [anchore/scan-action/download-grype](https://github.com/anchore/scan-action) from 7.4.0 to 7.4.1.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/RELEASE.md)
- [Commits](anchore/scan-action@e116508...8964e60)

---
updated-dependencies:
- dependency-name: anchore/scan-action/download-grype
  dependency-version: 7.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…ht in

#2059 renames `ledger/src/versions/common/**` onto `ledger_8/` but *adds* the
`ledger_9/` copy, so ledger_8 arrives with this branch's content while ledger_9
arrives with main's. That pulled `serialize_ledger_snapshot` and
`import_ledger_snapshot` into `ledger_9/mod.rs` — both from #1650 (warp-sync
ledger arena), one of the 16 commits #2023 deliberately excluded. They reference
`crate::SnapshotImportError`, which #1650 defines in `ledger/src/lib.rs` and this
branch therefore does not have, so the workspace did not compile.

Nothing calls either function on this branch. Removing them restores the parity
#2059 is about: `diff -r ledger/src/ledger_8 ledger/src/ledger_9` now differs
only in the version-binding aliases and docs.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
@ozgb
ozgb requested review from a team as code owners September 8, 2026 11:17
@ozgb
ozgb requested a review from a team as a code owner September 8, 2026 11:17
@ozgb ozgb added skip-changes-check-issue bot:ai-assisted Authored or substantially edited by an AI agent skip-changes-check-all and removed skip-changes-check-issue labels Sep 8, 2026
@datadog-official

This comment has been minimized.

ozgb and others added 4 commits September 8, 2026 12:36
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
…raits (#2080)

* fix: split system transaction executor into per-pallet allow-listed traits

Replace the single MidnightSystemTransactionExecutor trait with
MidnightSystemTransactionCNightExecutor and
MidnightSystemTransactionBridgeExecutor, so pallet-cnight-observation and
pallet-c2m-bridge can each only submit the SystemTransaction variants they
actually construct, instead of being able to pass through any variant the
ledger recognizes.

Each new trait is backed by a dedicated ledger host function
(apply_cnight_system_transaction / apply_bridge_system_transaction) that
deserializes once and checks its allow-list before applying, rather than a
separate classifier call ahead of apply_system_transaction. The governance
extrinsic path is rebuilt the same way onto a new
apply_governance_system_transaction host function. The previous
apply_system_transaction and is_governance_allowed_system_tx host functions
are kept in place, unused by new code, since they remain part of the
node<->runtime ABI for already-published runtime WASM and historical block
replay.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: add PR link to system-tx-executor-allow-lists change file

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* chore: rebuild metadata

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* fix: switch system tx apply log to debug

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

* docs: remove comment

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

---------

Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>
Signed-off-by: Oscar Bailey <79094698+ozgb@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e9e4f33d4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Earthfile

@jacek-kurkowski-shielded jacek-kurkowski-shielded left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gilescope a few comments:

  1. #2119 is missing.
  2. PR description does not mention #2080.
  3. .github/workflows/build-publish-partnerchains-dev.yml still exists on this branch. main deleted it in #1851
  4. #2058 pick pulled BABE docs into local-environment/README.md

Signed-off-by: Squirrel <giles.cope@shielded.io>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T16:43:54.804779Z 402b355 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

LGLO and others added 4 commits September 14, 2026 17:15
@gilescope

Copy link
Copy Markdown
Contributor

The 3 are now included. Apologies for including incorrect babe docs, these have been reverted in the last commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 402b3555cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread partner-chains/toolkit/data-sources/db-sync/src/bridge/mod.rs
@jacek-kurkowski-shielded

Copy link
Copy Markdown
Contributor

The 3 are now included. Apologies for including incorrect babe docs, these have been reverted in the last commit.

Awesome work @gilescope !

@jacek-kurkowski-shielded jacek-kurkowski-shielded left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@gilescope
gilescope merged commit dd1aded into release/node-2.1.0 Sep 15, 2026
34 of 35 checks passed
@gilescope
gilescope deleted the ozgb-backport-sept-maintenance branch September 15, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:ai-assisted Authored or substantially edited by an AI agent skip-changes-check-all

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants