Skip to content

feat: look up a synchronizer registration by contract id - #68

Open
haikoschol wants to merge 4 commits into
mainfrom
feat/registration-lookup-by-contract-id
Open

haikoschol wants to merge 4 commits into
mainfrom
feat/registration-lookup-by-contract-id

Conversation

@haikoschol

@haikoschol haikoschol commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Part of ChainSafe/canton-extending-mainnet#128. The SV UI that uses this endpoint is #67, which is stacked on this PR.

Summary

The offboard (SRARC_ArchiveSynchronizerRegistration) and set-parameters (SRARC_SetSynchronizerGovernanceParameters) votes pin a RegisteredSynchronizer by contract id, and that is all their payload carries. The only registration lookup was by synchronizer id. This PR adds a lookup by contract id to the SV app, which the SV UI in #67 uses to:

  • show what such a vote targets (synchronizer id, operator, current parameters);
  • flag an open vote whose registration is no longer active. A set-parameters vote recreates the registration under a new contract id, so any other open vote that pins the old one can no longer execute;
  • let an SV offboard the second of two duplicate registrations ([P2-E1.4] Registry uniqueness ChainSafe/canton-extending-mainnet#54), which the lookup by synchronizer id cannot reach because it returns only the lowest contract id.

Once the SV app holds the registrations, the existing lookup by synchronizer id (added in #59 for the register form's duplicate check) no longer needs to go through Scan either, so this PR serves both lookups from the SV app's own store.

It does not change Daml.

Changes

  • SvDsoStore ingests RegisteredSynchronizer. The DSO party signs every registration, so the SV app's own DSO store can hold them.
  • The SV app serves GET /v0/admin/sv/synchronizers/registrations/by-contract-id/{contract_id} (lookupSynchronizerRegistrationByContractId) from that store. It returns the registration only while its contract is active; an empty answer means the registration was archived (offboarded, or replaced by a parameters change).
  • GET /v0/admin/sv/synchronizers/{synchronizer_id}/registration (lookupSynchronizerRegistration) is served from the same store instead of being forwarded to Scan. If an id is registered twice, which the ledger does not prevent, it returns the lowest contract id, as Scan does. Path and response shape are unchanged; the response schema is now defined in sv-internal.yaml instead of referencing scan.yaml, so the SV UI needs no change.
  • DB migration: V077__dso_acs_store_registered_synchronizer.sql adds a nullable registered_synchronizer_id text column to dso_acs_store, filled at ingestion, for the lookup by synchronizer id. SqlIndexInitializationTrigger creates the matching partial index dso_acs_store_sid_mid_pn_tid_rsid concurrently. This mirrors how Scan indexes the same query (V076 + scan_acs_store_sid_mid_pn_tid_rsid). The lookup by contract id needs no index column.

An earlier revision forwarded the contract-id lookup to a new Scan endpoint. That endpoint is removed again (see review). Scan keeps its lookup by synchronizer id, which the validator, the wallet and the scan proxy still use; this PR does not change Scan.

Testing

  • DbSvDsoStoreTest: a RegisteredSynchronizer is found by contract id and by synchronizer id among other registrations, is found by neither once archived, an unknown synchronizer id finds nothing, and a synchronizer id registered twice always resolves to the lowest contract id.
  • SqlIndexInitializationTriggerStoreTest expects the new index.
  • register-synchronizer-form.test.tsx passes unchanged.
  • apps-sv, apps-scan, apps-common and apps-app (incl. test sources) compile and pass scalafmtCheck.

Note for deployment

Adding a template to the DSO store's ingestion filter does not re-ingest contracts created before the upgrade: the store descriptor does not cover the filter. An SV app that upgrades to this change only sees registrations created afterwards; earlier ones stay missing until the DSO store is reset via dsoAcsStoreDescriptorUserVersion. This now also affects the register form's duplicate check, which would not flag an id registered before the upgrade. That is harmless if this ships no later than the first release with registrations, which is the 0.10.0 Daml cut.

The migration needs no backfill, because dso_acs_store held no RegisteredSynchronizer rows before this PR. The column is nullable, so adding it does not rewrite the table, and the index is built concurrently after startup.

Offboard and set-parameters votes pin a RegisteredSynchronizer by contract
id, which is all their payload carries. Scan now serves an active
registration by contract id, and the SV app forwards it, so the SV UI can
show what such a vote targets, flag one whose registration was archived,
and let an SV offboard the second of two duplicate registrations, which
the lookup by synchronizer id does not reach.

Empty means archived: offboarded, or replaced by a parameters change.

Signed-off-by: Haiko Schol <haiko@chainsafe.io>
The SV app forwarded the lookup to Scan. The DSO party signs every
RegisteredSynchronizer, so the SV app's own DSO store can hold them:
ingest them there and look the contract up locally, which avoids a BFT
call across all scans for an SV-local read and the dependency on Scan
being reachable.

Nothing else uses the Scan endpoint added for this, so it is removed
again, along with its client chain, console method and integration-test
assertion. The SV endpoint keeps its path and response schema, now
defined in sv-internal.yaml.

Signed-off-by: Haiko Schol <haiko@chainsafe.io>

@moritzkiefer-da moritzkiefer-da 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.

thx while you're at it can you acutally change the one from #59 as well, I missed the scan indirection there.

…re [ci]

The SV app forwarded the lookup by synchronizer id, which the SV UI uses
to reject a duplicate registration, to Scan. The SV app's DSO store now
ingests every RegisteredSynchronizer, so look it up there instead: an
SV-local read no longer needs a BFT call across all scans or a reachable
Scan.

The lookup filters on a new dso_acs_store.registered_synchronizer_id
column, populated at ingestion, the way Scan serves the same query: V077
adds the column and SqlIndexInitializationTrigger adds the concurrent
index. order by contract_id keeps the answer deterministic when an id is
registered twice, which the ledger does not prevent.

Scan keeps its endpoint: the validator, the wallet and the scan proxy
still use it. The SV endpoint keeps its path and response shape, now
defined in sv-internal.yaml.

Signed-off-by: Haiko Schol <haiko@chainsafe.io>

This branch has not been deployed

No deployments
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.

2 participants