Skip to content

feat(config): begin and end a registry migration with update-config, and read a Config that grew - #34

Open
rssh wants to merge 3 commits into
mainfrom
feat/config-registry-revision
Open

rssh wants to merge 3 commits into
mainfrom
feat/config-registry-revision

Conversation

@rssh

@rssh rssh commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Spec rev 5.6 (ft-bifrost-bridge #56, [CFG-10]) revises the SPO registry without redeploying the bridge. One governance Update moves Config #9 to the new registry, moves #8 to its ban list, and appends #13 previous_spos_registry_policy_id, the registry pools are crossing from. heimdall carries registrations across by itself (lantr-io/heimdall#114). This PR is binocular's part of that Update, and it makes sure binocular keeps working once the Config has fourteen fields.

update-config

The rollout's step c3 is therefore:

update-config --migrate-registry-to <new registry policy> --spo-bans-policy <new ban policy>
# … pools cross …
update-config --end-registry-migration

Reading a Config that grew

update-config used to refuse any Config longer than the rev 5.5 layout, because re-encoding it would drop the appended field. Refusing was safe, but it made #13 impossible to write, and impossible to write any other field once #13 exists. DeployedConfig now carries appended fields verbatim, and update-config, the proof service, the sweep setup and the PoR sweeper all decode through it.

ConfigDatum is deliberately unchanged, still the thirteen-field mirror. TreasuryMovementValidator decodes it on chain, so a fourteenth field would move that validator's pinned hash. That would mean a new TM script for a change that has nothing to do with it.

The deployed TM validator and #13

This was the real risk: the TM validator is already deployed, and it reads the Config. New tests evaluate the pinned blueprint program, the exact UPLC on chain, on both the confirm and mint paths, against a Config with #13 appended, once mid-migration and once ended. It accepts all four. So the migration's Update does not stop treasury movements on a live bridge.

Which contracts a bridge runs: bridge.contracts

binocular now carries two contracts releases: rev 5.5 (ft main, what the preprod bridge runs) and rev 5.6 (ft 096f76c, what heimdall embeds). bridge.contracts in the config file says which one the bridge was deployed with. It defaults to rev5.5, so existing config files keep working, and the preprod config now states it explicitly.

It's a property of the bridge, fixed at genesis, and a registry revision does not change it. The revision replaces the registry and the ban list, which heimdall deploys. The Config, peg and TM scripts that binocular spends stay the ones the bridge was deployed with.

The vendored blueprint carries its release, and the release decides:

The rev 5.6 policy ids are pinned against heimdall's independent Rust derivation from the same bytes, the same cross-check that already locks rev 5.5.

After a revision: deploy-script-refs and register-bridge-creds

Both rebuilt the registry and ban list from #12 and refused unless they matched #9 and #8, so after a revision both would have failed on the live bridge. They now classify the Config:

  • The treasury still matches but the registry has moved: that's a revision. They skip the SPO half with a message, because heimdall publishes those reference scripts and registers that credential.
  • A wrong one-shot still fails, on the treasury.

bridge.plutus-json

It used to default to ../../FluidTokens/ft-bifrost-bridge/onchain/plutus.json and fall back to the packaged blueprint silently when that file was absent. The contracts a command built with therefore depended on whether, and on which branch, an ft checkout sat next to the working directory. On a machine whose checkout is on the rev 5.6 branch, a genesis would have deployed rev 5.6 without a word. It now defaults to empty, meaning the packaged blueprint of contracts, and a path that can't be read is refused.

docs/operator-guide.md

binocular's first guide for the people who run a bridge. It covers:

  • what binocular does for one;
  • the bridge section (contracts above all);
  • genesis in order across heimdall and binocular;
  • the watchtower;
  • changing the Config, including the registry migration;
  • what the common refusals mean.

The README's command table, which listed commands that no longer exist, now points to it.

Not covered here

  • Whether the deployed config.ak accepts an Update that appends a field. The spec says config.ak accepts any datum shape, but that has to be seen on chain. The preprod rehearsal (a separate test bridge migrated end to end before release) is where it will be.
  • A rev 5.6 genesis on chain. It is pinned offline against heimdall's derivation, but it hasn't been deployed yet.

Tests

All 724 pass under sbt testFull after the full cache reset. Plain sbt test in sbt 2 is incremental: it reported 125 and 621 on earlier runs of this branch, which is exactly the partial-run trap binocular's CLAUDE.md warns about. The freshness check now passes whichever of the two vendored releases the neighbouring ft checkout is on.

…and read a Config that grew

A registry revision (spec rev 5.6, [CFG-10]) is one governance Update that moves Config #9 to the new registry and appends #13, the registry pools are crossing from. update-config now does exactly that. `--migrate-registry-to` writes #9 and takes #13 from the value #9 held, so it cannot be mistyped, and it insists on `--spo-bans-policy` in the same Update, because the ban list moves with the registry. `--end-registry-migration` empties #13 without removing it.

A Config longer than the rev-5.5 layout used to be refused by update-config, because re-encoding it would drop the appended field. DeployedConfig now carries appended fields verbatim, and update-config plus the three off-chain readers decode through it. ConfigDatum stays the thirteen-field mirror, because TreasuryMovementValidator decodes it on chain and a fourteenth field would move its pin. New tests evaluate the deployed TM blueprint program, confirm and mint alike, against a Config with #13 appended, both mid-migration and ended: the live validator accepts it, so the migration's Update does not stop treasury movements.
… operator guide

A bridge's contracts release is fixed at its genesis, and binocular now carries two: rev 5.5 (ft main, the preprod bridge) and rev 5.6 (ft 096f76c, the release heimdall embeds). `bridge.contracts` says which one a bridge was deployed with, and it defaults to rev5.5. The vendored blueprint for that release travels with its bytes, and it decides the two version-dependent parameterizations: the registry's added Config policy, and the ban list's Config policy in place of the registry hash. It also decides the Config arity genesis writes (13, or 14 with #13 empty). The rev 5.6 policy ids are pinned against heimdall's independent Rust derivation. A registry revision does not change the release. deploy-script-refs and register-bridge-creds now recognise a Config whose treasury still matches but whose registry has moved, and leave that half to heimdall instead of refusing. `bridge.plutus-json` defaults to empty and is refused when it cannot be read. It used to default to a sibling ft checkout and fall back silently, which made the contracts a command built with depend on that checkout's branch.

docs/operator-guide.md is binocular's first guide for the people who run a bridge. It covers what binocular does for one, the `bridge` section, genesis in order across heimdall and binocular, the watchtower, and changing the Config (including the registry migration). It ends with what the common refusals mean. The README's command table, which listed commands that no longer exist, now points to it.
The guide's revision section now gives the order: operators upgrade heimdall at their own pace, the new version runs the unrevised bridge unchanged, and the governance Update comes once every node runs it.
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