From cfe53d7c5c2bc1ba422f81b0fcb57c699757891d Mon Sep 17 00:00:00 2001 From: danielporterda Date: Wed, 1 Jul 2026 15:55:04 -0400 Subject: [PATCH] Add Canton release note updater Signed-off-by: danielporterda --- docs-main/docs.json | 26 +- .../release-notes/canton-releases/3-5-1.mdx | 901 +++++++++++++++++ .../release-notes/canton-releases/3-5-2.mdx | 74 ++ .../release-notes/canton-releases/3-5-3.mdx | 34 + .../release-notes/canton-releases/3-5-4.mdx | 55 ++ .../release-notes/canton-releases/3-5-5.mdx | 72 ++ .../release-notes/canton-releases/3-5-6.mdx | 82 ++ .../release-notes/canton.mdx | 917 +----------------- package.json | 1 + scripts/summarize_version_changes.py | 23 + scripts/update_canton_release_notes.py | 459 +++++++++ scripts/update_generated_reference_prs.py | 31 +- tests/test_update_canton_release_notes.py | 214 ++++ tests/test_update_generated_reference_prs.py | 2 + 14 files changed, 1980 insertions(+), 911 deletions(-) create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-1.mdx create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-2.mdx create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-3.mdx create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-4.mdx create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-5.mdx create mode 100644 docs-main/global-synchronizer/release-notes/canton-releases/3-5-6.mdx create mode 100644 scripts/update_canton_release_notes.py create mode 100644 tests/test_update_canton_release_notes.py diff --git a/docs-main/docs.json b/docs-main/docs.json index c354d0635..6b9db00d6 100644 --- a/docs-main/docs.json +++ b/docs-main/docs.json @@ -462,7 +462,18 @@ "group": "Release Notes", "pages": [ "global-synchronizer/release-notes/splice", - "global-synchronizer/release-notes/canton" + { + "group": "Canton", + "pages": [ + "global-synchronizer/release-notes/canton", + "global-synchronizer/release-notes/canton-releases/3-5-6", + "global-synchronizer/release-notes/canton-releases/3-5-5", + "global-synchronizer/release-notes/canton-releases/3-5-4", + "global-synchronizer/release-notes/canton-releases/3-5-3", + "global-synchronizer/release-notes/canton-releases/3-5-2", + "global-synchronizer/release-notes/canton-releases/3-5-1" + ] + } ] }, { @@ -3211,7 +3222,18 @@ "group": "Canton Network", "pages": [ "global-synchronizer/release-notes/splice", - "global-synchronizer/release-notes/canton" + { + "group": "Canton", + "pages": [ + "global-synchronizer/release-notes/canton", + "global-synchronizer/release-notes/canton-releases/3-5-6", + "global-synchronizer/release-notes/canton-releases/3-5-5", + "global-synchronizer/release-notes/canton-releases/3-5-4", + "global-synchronizer/release-notes/canton-releases/3-5-3", + "global-synchronizer/release-notes/canton-releases/3-5-2", + "global-synchronizer/release-notes/canton-releases/3-5-1" + ] + } ] }, { diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-1.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-1.mdx new file mode 100644 index 000000000..64546dc34 --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-1.mdx @@ -0,0 +1,901 @@ +--- +title: "3.5.1" +description: "Canton 3.5.1 release notes." +--- + +# Release of Canton 3.5.1 + +Canton 3.5.1 has been released on May 27, 2026. + +## What’s New + +### Contract Keys + +#### Overview +Canton 3.5 introduces contract keys. Compared to a similar feature available in Canton 2.x, there are two notable +differences: + +- The keys are not unique, meaning multiple contracts may share the same key. +- Negative lookups are not validated. + +As a consequence, application developers must ensure key uniqueness through external enforcement mechanisms. +Contract keys are available from Daml-LF 2.3 onwards, which itself is available from Protocol Version 35, see below. + +#### Standard library +Daml language now supports several primitives associated with contract keys. In all cases, the contracts are returned +in the following order: +- first the contracts created within a transaction, starting with the most recent, +- then explicitly disclosed contracts, +- then contracts known to the participant in recency order. + +The following primitives are available: + +- ``lookupByKey`` - Available in prelude. It checks whether a contract with the given key exists and if yes, returns + the contract id. If multiple contracts exist, the most recently created is returned. Signature is the same as + in 2.x. +- ``fetchByKey`` - Available in prelude. It fetches the first contract id and contract data associated with the given + contract key. If multiple contracts exist, the most recently created is returned. Signature is the same as in 2.x. +- ``exerciseByKey`` - Available in prelude. Exercise a choice on the first contract associated with the given key. + Signature is the same as in 2.x. +- ``lookupNByKey`` - Available in ``DA.ContractKeys``. It looks up up to n contracts associated with the passed key. + +#### Daml Script +There are Daml Script functions - counterparts of the standard library primitives: + +- ``queryByKey`` - It looks up a contract associated with the passed key and returns its ids and data. It is of type + ``Script``, which means it must appear as top-level instruction as part of a Script. +- ``queryNByKey`` - It looks up up to n contracts associated with the passed key and returns their ids and data. + It is of type ``Script``, which means it must appear as top-level instruction as part of a Script. +- ``exerciseByKeyCmd`` - It exercises a choice on the first contract with the given key. It is of type ``Commands`` and + must therefore be wrapped by a submit operation, and can be combined with other ``Commands``. + + +#### Smart Contract Upgrades (SCU) +To support SCU upgrade for key and maintainer definitions, new guidelines have been added. At upgrade time, the recomputed +key and maintainers are verified to be identical to the upgraded contract’s original key and maintainers. If they +aren't, an upgrade error is raised and the transaction is aborted. +It is forbidden to add or remove a key definition from a template in a later version of that template. This is enforced +at package vetting time. + +#### Ledger API +Following contract-key related extensions have been made to the Ledger API + +- ``contract_key_hash`` has been added to the ``CreatedEvent`` message returned in the ``State-`` and ``UpdateService`` + responses +- ``prefetch_contract_keys`` field present in the ``Command`` and ``PrepareSubmissionRequest`` used by the ``Command-`` + ``CommandSubmission-`` and ``InteractiveSubmissionService`` have been reactivated to allow the caller to request + pre-heating the contract key cache underpinning the command interpretation. Use it when performance tests indicate + that many sequential contract key lookups adversely impact the command interpretation speed. + +#### PQS +In PQS, keys are mere metadata that can be queried like any other metadata. It is possible to query for all contracts +with a given key: + + ``` + select contract_id, payload ->> 'label' + from __contracts + where contract_key = jsonb_build_object(...) + order by created_at_ix + ``` + +### Daml-LF 2.3 + +A new version of Daml-LF is released: Daml-LF 2.3. Its main features are: + +- [`DA.Crypto.Text`](https://docs.digitalasset.com/build/3.5/reference/daml/stdlib/DA-Crypto-Text.html), + originally released in 3.4 in early access (alpha) status, is part of LF 2.3, + which means it is now marked as stable. +- Support for Contract Keys. + +#### Targeting LF 2.3 + +If you want to use new features available in the LF 2.3, select it explicitly as compilation target by setting the +`--target=2.3`, either as direct argument on the command line or as part of a `daml.yaml`: + +``` +sdk-version: 3.5.1 +name: some-name +source: daml +version: 0.0.1 +dependencies: + - daml-prim + - daml-stdlib +build-options: +- --target=2.3 +``` + +After changing the settings, the source code must be recompiled. Please note that this will cause the package id to +change, which should be accompanied by a version change. + +### Logical Synchronizer Upgrades +Logical Synchronizer Upgrades, or LSU, replace the procedure previously used to upgrade the synchronizer (Synchronizer +Upgrade with Downtime). LSU address the following shortcomings of the previous upgrade procedure: + +- Reduced downtime + + The downtime for Daml transactions is in the order of dozens of seconds. + The downtime for topology transactions is in the order of hours before the upgrade. + This will be improved in a subsequent version. + +- No manual coordination for validators + + Operators of validator nodes only need to upgrade their binary before the upgrade time. + The rest of the procedure happens through automation + +- Reduced coordination for SVs + + SVs can progress independently with the preparation of the upgrade and signal their progress using dedicated + topology transactions. + +- Asynchronous upgrade of each validator + + Each validator independently upgrade its binary before the upgrade time. + Each validator automatically perform the upgrade when it processes the last message of the old synchronizer. + +- Preserved local history as well as cryptographic evidence + + Transaction history (including updates and their offsets) is preserved, as well as ACS commitments. + +#### External submissions around upgrade time +External submissions prepared before upgrade time on a synchronizer running protocol version 34 cannot be +submitted after LSU on a synchronizer running protocol version 35: they need to be re-prepared and re-signed. + +### DA BFT Beta + +DA BFT is a new ordering service as part of the synchronizer that will replace the current single-leader CometBFT ordering service on the Global Synchronizer with a parallel, multi-leader consensus architecture, enabling significantly higher transaction throughput and fault tolerance. + +As part of this release DA BFT is ready in beta form for early access testing, but not recommended for production or close-to-production testing yet. + +### Multi Synchronizer Alpha + +Multi-synchronizer support is available in early access and has to be enabled explicitly. +This feature should only be used in test environments. + +To enable contract reassignment across synchronizers, the flag `PARTICIPANT_FEATURE_FLAG_ENABLE_ALPHA_MULTI_SYNCHRONIZER` must be activated on all participants hosting a stakeholder of the contract on both the source and target synchronizers. For a synchronizer, it can be done as follows: + +``` +participant.topology.synchronizer_trust_certificates.propose( + p.id, + synchronizerId, + featureFlags = Seq(ParticipantTopologyFeatureFlag.EnableAlphaMultiSynchronizer), +) +``` + + +## Functional Changes + +### Party Replication + +#### Offline party replication + +Concluding an offline party replication by clearing the onboarding flag now includes two major updates +when using protocol version 35: +- Added crash resilience for ongoing clearances. +- Automatic scheduling for clearances when a participant (re)connects to the synchronizer. + +These changes apply only to the `participant.parties.import_party_acs` and +`participant.parties.clear_party_onboarding_flag` endpoints. + +Note: The replicated party ID must be included in the party ACS import call to enable automatic +scheduling. The original behaviour is retained for protocol version 34. + +#### Party replication onboarding topology event is exposed on Ledger API + +The `PartyToParticipant` topology "onboarding" state used in the process of replicating a party with existing +contracts is now visible via the Ledger API when a party onboards on a synchronizer on protocol version 35 or higher. +Starting with PV=35, the newly introduced `ParticipantAuthorizationOnboarding` Ledger API topology event signals +the beginning of party replication and transitions to `ParticipantAuthorizationAdded` once the party's ACS is fully +visible on the Ledger API. + +#### Preview: Online party replication + +- Added the file-based online party replication command `participant.parties.add_party_with_acs_async` to + be used along with `participant.parties.export_party_acs` and instead of the sequencer-channel-based + `add_party_async` command. +- The online party replication status command now returns status in a very different, "vector-status" format + rather than the old "oneof" style. This impacts the `participant.parties.get_add_party_status` command and + `com.digitalasset.canton.admin.participant.v30.PartyManagementService.GetAddPartyStatus` gRPC response type. +- The participant configuration to enable online party replication has been renamed to + `alpha-online-party-replication-support` from `unsafe-online-party-replication` for consistency with other + alpha features and to reflect that the default file-based mode is more secure not relying on sequencer + channels. +- The sequencer configuration to enable sequencer channels for online party replication has been renamed to + `unsafe-sequencer-channel-support` from `unsafe-enable-online-party-replication` for consistency and to + refer specifically to sequencer channels. + +#### Minor Improvements + +- Onboarding party submission prevention: Ensures a participant does not submit a transaction or reassignment on behalf + of an onboarding party. +- Upgraded gRPC to 1.81.0 and AWS SDK to 2.44.3 to resolve Netty 4.1.130 CVEs (CVE-2026-33870, CVE-2026-33871). + +### New Transaction Hashing Scheme v3 + +- A new hashing scheme version `HASHING_SCHEME_VERSION_V3` has been introduced that includes the transaction's `max_record_time` in the hash computation and covers the new transaction node and fields of contract keys. This new version is avaialble from Protocol Version 35. +- See the [hashing algorithm documentation](https://docs.digitalasset-staging.com/build/3.5/explanations/external-signing/external_signing_hashing_algorithm.html#summary-of-differences-between-v2-and-v3) for the updated version. +- The `max_record_time` is now enforced by all confirming participants. +- The Ledger API and Ledger JSON API prepare `InteractiveSubmissionService` has been modified to take in a specific hashing scheme version in the request. +The default hashing scheme is `HASHING_SCHEME_VERSION_V2`. Integrators are encouraged to move to `HASHING_SCHEME_VERSION_V3` for synchronizers using protocol version 35. +In particular, usage of **contract keys** requires `HASHING_SCHEME_VERSION_V3`. See the versioning [documentation](https://docs.digitalasset-staging.com/build/3.5/explanations/external-signing/external_signing_hashing_algorithm.html#hashing-scheme-version) for details. + +### Active Contracts Head Snapshot (ACHS) + +The Active Contracts Head Snapshot (ACHS) is a new optional feature that maintains a continuously updated snapshot of +the currently active contracts. When enabled, the ACHS accelerates `GetActiveContracts` (ACS) queries by allowing them +to read directly from a pre-computed snapshot rather than scanning the full event log to reconstruct the active set. + +ACHS is disabled by default. To enable it, configure the `achs-config` block under the participant's indexer settings: +``` +canton.participants..parameters.ledger-api-server.indexer.achs-config { + valid-at-distance-target = 1000000 + last-populated-distance-target = 500000 +} +``` + +The `valid-at-distance-target` controls how far behind the ledger end (in event sequential IDs) the snapshot's validity +point is maintained. The ACHS is not used for serving queries below its validity point, logging at INFO level "ACHS for <filter> +skipped since validAt (...) already surpassed requested activeAt (...)". If the `valid-at-distance-target` +value is too small, long-running ACS queries may observe the ACHS validity point +moving (mid-stream) past their requested offset, causing the stream to fall back to the slower filter tables query, logging +at INFO level "ACHS stream for <filter> fell back to filter tables from (...) since validAt (...) surpassed activeAtEventSeqId (...)". If +the value is too large, the tail portion of the ACS (between the ACHS validity point and the requested offset) must be +resolved from the filter tables, making that last segment more expensive. + +As described above, when the ACHS validity point moves or is past the requested offset, an info-level log message is +emitted indicating that the stream fell back to the filter tables. +Two corresponding metrics, `achs_skips` and `achs_midstream_fallbacks`, are available under `daml.participant.api.index` +to help operators monitor the frequency of these fallbacks and tune the `valid-at-distance-target` accordingly. + +The `last-populated-distance-target` controls the additional lag (in event +sequential IDs) for the population of ACHS in order to store only the long-lived contracts. A larger value reduces +database I/O by skipping short-lived contracts that are created and archived before they would be added to the snapshot. +However, setting it too large increases the cost of the remaining ACS tail, as more data must be fetched from the filter +tables to cover the gap between the last populated point and the ACHS validity point. + +Further tuning parameters include: +- `population-parallelism`: number of parallel threads for adding activations to the ACHS during normal operation. +- `removal-parallelism`: number of parallel threads for removing deactivated activations from the ACHS during normal operation. +- `aggregation-threshold`: minimum batch size (in event sequential IDs) before ACHS maintenance work is emitted. +- `init-parallelism`: number of parallel threads for ACHS population and removal during initialization. +- `init-aggregation-threshold`: minimum batch size (in event sequential IDs) for ACHS maintenance during initialization. +- `buffer-size`: size of the internal buffer between the indexer pipeline and the ACHS maintenance flow. + +The `deactivation_distances` histogram metric which is available under `daml.participant.api.indexer.deactivation_distances` +can help operators understand the distribution of contract lifetimes (the event sequential ID distance between a contract's +activation and its deactivation) and set an appropriate `last-populated-distance-target`. Ideally, the population distance +should be large enough so that most short-lived contracts are already deactivated and thus not added to the snapshot. + +Three gauge metrics are available under `daml.participant.api.indexer` to monitor the ACHS state: +- `achs_valid_at`: the event sequential ID at which the ACHS is currently valid. ACS queries with a requested offset + at or after this value can read directly from the ACHS. +- `achs_last_populated`: the last event sequential ID for which activations were added to the ACHS. +- `achs_last_removed`: the last event sequential ID for which deactivations were looked up and the corresponding + activations were removed from the ACHS. + +### Hardened Error Handling in Sequencer Connect Service + +We have implemented strict error sanitization and rewording for the SequencerConnectService to mitigate information leakage. +Detailed internal error messages are now redacted before being sent to clients. + +If detailed diagnostics are required in a non-production environment, sanitization can be toggled off via: + +``` +canton.monitoring.sanitize-public-error-messages = false +``` + +### Ignoring of offboarded sequencers for submission requests + +In the case where sequencers are offboarded but remain online and kept in the connectivity configuration, it was still possible that members pick them as the target for submission requests. The submission would fail, but the member would incur a delay as it requires retrying. +This has now changed, and offboarded sequencers are ignored when sending submission requests. + +#### API Changes + +The previous method of returning errors via response fields has been removed in favor of canonical gRPC error propagation. +The following fields are now obsolete: + +- `HandshakeResponse.value.failure` +- `VerifyActiveResponse.value.failure` + +Errors are now communicated strictly through `io.grpc.Status` codes to ensure a consistent and secure interface. + +Status codes have changed as follows: + +- SequencerAuthenticationService.challenge newly fails with `INVALID_ARGUMENT` (instead of `FAILED_PRECONDITION`), + if the client does not support the sequencer's protocol version. +- SequencerConnectService newly fails with `INVALID_ARGUMENT` (instead of `FAILED_PRECONDITION`) if a non-participant tries to connect. +- SequencerConnectService.registerOnboardingTopologyTransactions newly fails with `INTERNAL` (instead of `FAILED_PRECONDITIONS`) +- if there are no dynamic synchronizer parameters. +- SequencerConnectService.registerOnboardingTopologyTransactions newly fails with `FAILED_PRECONDITION` if +- the transactions cannot be added to the topology state and sanitization of error messages is enabled. + +### Mediator Crash Fault Tolerance + +The mediator is now crash fault-tolerant and guarantees that all verdicts will eventually be persisted and available on the inspection API. + +### Enhanced Reliability for `GetHighestOffsetByTimestamp` + +Previously, the `GetHighestOffsetByTimestamp` RPC and the `find_highest_offset_by_timestamp` console command could return offsets not yet synced with the participant's local cache. Furthermore, forcing a query with a future timestamp resulted in an error. + +Specific changes: +- The required state is now retrieved atomically via a consistent database snapshot. +- The endpoint now includes an internal barrier (waiting up to 10 seconds) to ensure the local Ledger API cache catches up with the database before returning the offset. +- When `force` is true, requesting a future timestamp now gracefully returns the current ledger end instead of failing. + +No migration required. + +### ACS stream continuation + +The `GetActiveContracts` stream request has been extended with an optional `stream_continuation_token` field that allows +clients to continue an interrupted ACS stream from the last element which made through. The field can be populated with +the `stream_continuation_token` field of the last response element received before the interruption, and the stream will +continue from the next element after that. + +### ACS Ledger API counting + +Introduced a new memory-efficient consoled command `participant.ledger_api.acs.count()` +to count the number of active contracts on a participant node. + +> Note: This command is currently under the Testing feature flag. + +### ACS pagination + +A new, `GetActiveContractsPage` endpoint added to State Service API. This enables the client to retrieve the ACS in +paginated form, by specifying a `max_page_size`. The pages can be accessed sequentially by using the `page_token` +field. The token can be obtained from the `GetActiveContractsPageResponse` of the last page. + +### GetUpdates stream in descending order of events + +The `GetUpdatesRequest` object has new optional parameter `descending_order`. When this parameter is `true` the events +are streamed from the newest to the oldest ones. The pages can be accessed sequentially by using the `page_token` +field. + +### GetUpdates pagination +A new `GetUpdatesPage` endpoint has been added to Update Service API. THis allows retrieval of updates in paginated +form instead of requesting the stream. + +### Improvements for `repair.add` and migration advice + +The `participant.repair.add` admin command has been revised to use the new `ImportAcs` backend, bringing significant +memory performance improvements, stricter default safety validations, and several new parameters. + +#### Important behavioral change: strict `Validation` by default + +Previously, `repair.add` implicitly accepted all injected contracts without re-evaluating their cryptographic hashes. To +prevent accidental data corruption, the command now defaults to **Validation** mode ( +`contractImportMode = ContractImportMode.Validation`). + +- **Impact:** If you have existing scripts or recovery procedures that inject manually modified, synthetic, or + inconsistent contracts (where the payload does not strictly match the `ContractId` hash), they will now fail with a + `"Failed to authenticate contract with id"` error. +- **Migration:** To bypass this cryptographic validation and restore the legacy behavior, explicitly pass the `Accept` + mode in your command call: + ```scala + participant.repair.add( + synchronizerId = mySynchronizer, + protocolVersion = myProtocolVersion, + contracts = myContracts, + contractImportMode = ContractImportMode.Accept // Bypasses strict validation + ) + ``` + +#### New parameters + +The command signature has been expanded to support several optional parameters: + +- `workflowIdPrefix`: Allows you to set a custom prefix for the generated workflow ID to easily track the repair + transactions (defaults to `import-`). +- `contractImportMode`: Choose between `Validation` (default, validates that contract IDs comply with the scheme + associated to the synchronizer where the contracts are assigned), or `Accept` the contracts as they are (if you know + what you are doing). +- `representativePackageIdOverride`: Allows you to remap or override the representative package IDs of the contracts as + they are imported. +- `excludedStakeholders`: When defined, any contract that has one or more of these parties as a stakeholder will not be + added. + +### Improved party and repair ACS imports + +We have completely overhauled the ACS import endpoints for both party replication and participant repair to be +memory-efficient streaming endpoints: + +- Console command `participant.parties.import_party_acs` +- Console command `participant.repair.import_acs` +- gRPC RPC `PartyManagementService.ImportPartyAcs` +- gRPC RPC `ParticipantRepairService.ImportAcs` + +This resolves previous memory limitations, as these endpoints no longer load the entire ACS snapshot into memory at +once. + +#### Action required: Breaking API change + +The `synchronizerId` is now a **mandatory** first parameter for both the `import_party_acs` and `import_acs` console +commands as well as their analogous gRPC endpoints. You will need to update any existing scripts. + +**For `import_party_acs`:** + +- **Old usage:** `participant.parties.import_party_acs("canton-acs-export.gz")` +- **New usage:** `participant.parties.import_party_acs(mySynchronizerId, importFilePath = "canton-acs-export.gz")` + +**For `import_acs`:** + +- **Old usage:** `participant.repair.import_acs("canton-acs-export.gz")` +- **New usage:** `participant.repair.import_acs(mySynchronizerId, importFilePath = "canton-acs-export.gz")` + +Because of the mandatory `synchronizerId` parameter, to import a multi-synchronizer ACS snapshot, you must now call the +endpoint sequentially for each synchronizer your participant is connected to, using the exact same snapshot file. The +import process will ignore any contracts in the snapshot that are associated to a different synchronizer. + +##### Details on the gRPC `ImportAcs` repair endpoint + +The `ImportAcs` and `ImportAcsV2` RPCs have been consolidated, introducing the following breaking changes and migration +steps: + +- **Endpoint removed:** `ImportAcsV2` (along with its request/response messages) is completely removed. All clients must + migrate to the standard `ImportAcs` RPC. +- **Request signature and type changes:** + - Fields `workflow_id_prefix` (2), `contract_import_mode` (3), and `representative_package_id_override` (5) in + `ImportAcsRequest` are now explicitly `optional`. + - A new `optional string synchronizer_id = 6` field was added. + - **Migration (ScalaPB):** Adding `optional` changes generated code from base types to `Option[T]`. Existing clients + will fail to compile and must be updated to wrap assigned values (e.g., `workflowIdPrefix = Some("prefix")`) and + explicitly handle reading `Option` types. +- **Behavioral change (`synchronizer_id`):** When filtering by synchronizer, mismatched contracts are now ignored. This + breaks previous logic that relied on the import strictly aborting upon a mismatch. + +##### Details on the gRPC `ImportPartyAcs` party replication endpoint + +The `ImportPartyAcs` endpoint underwent the exact same consolidation (removing `ImportPartyAcsV2`), streaming semantics +updates, generated code changes (ScalaPB `Option[T]`), and mismatched synchronizer behavior (ignoring rather than +failing) as `ImportAcs`. + +**Key differences specific to `ImportPartyAcs`:** + +- **New capability (`party_id`):** A new `optional string party_id = 6` field was added. Providing this in the first + request of the stream enables automatic, crash-resilient scheduling of the onboarding flag clearance. If omitted, the + participant logs a warning, and the flag must be cleared manually. + +### Topology-Aware Package Selection (TAPS) improvements + +Topology-Aware Package Selection (TAPS) refinement for handling inconsistent vetting states: +- The algorithm now considers a party's package vetting state only for packages required by that party in the interpreted transaction. + It starts with a minimal set of restrictions derived from the command's root nodes and progressively accumulates more restrictions over a configurable number of passes. + This iterative process increases the likelihood of finding a valid package selection set for the routing of the transaction. +- The maximum number of TAPS passes can be set at the request-level via the optional `taps_max_passes` field in `Commands` or `PrepareSubmissionRequest` messages. + If not specified, the default value is taken from the participant configuration via `participants.participant.ledger-api.topology-aware-package-selection.max-passes-default` (defaults to `3`). + A hard limit is enforced by `participants.participant.ledger-api.topology-aware-package-selection.max-passes-limit` (defaults to `4`). +- TAPS now ignores unvetted dependencies of packages that are not required for interpretation. + complying now with the support of unvetted dependencies in the Canton protocol. + +### Ledger API Improvements + +- ApiRequestLogger now also used by Ledger JSON Api. Changes: + - Redundant Request TID removed from logs. + - Additional CLI options added: `--log-access` captures API access logs in a separate file (default: `log/canton_access.log`), and `--log-access-errors` captures API access errors in a separate file (default: `log/canton_access_error.log`). + - Additional config options added: `debugInProcessRequests` logs in-process gRPC requests at DEBUG instead of TRACE, and `prefixGrpcAddresses` prefixes gRPC client addresses with `grpc:` (enabled by default). +- LedgerAPI ListKnownParties supports an optional prefix filter argument filterParty. + The respective JSON API endpoint now additionally supports `identity-provider-id` as + an optional argument, as well as `filter-party`. +- Protect the admin participant from self lock-out. It is now impossible for an admin to remove own admin rights or + delete itself. +- On Ledger API interface subscriptions, the `CreatedEvent.interface_views` now returns the ID of the package containing + the interface implementation that was used to compute the specific interface view as `InterfaceView.implementation_package_id`. +- OffsetCheckpoints are now always generated when an open-ended updates or completions stream is requested, even if there + are no updates. The checkpoint can have the same offset as the exclusive start of the stream, making checkpoints visible + even when starting from the ledger end. This enables client systems to recognize when the ledger end is advancing, + even if the stream of updates is inactive. +- Extended the set of characters allowed in user-id in the ledger api to contain brackets: `()`. + This also makes those characters accepted as part of the `sub` claims in JWT tokens. +- Functionality for managing internal and external parties has been improved, removing previous asymmetry: + - User rights can now be assigned to an external party during allocation. + - External parties can be allocated by the user themselves in the self-administration mode. + Please note that users in self-administration mode can allocate up to N parties, depending on a setting of the parameter + ``` + canton.participants..ledger-api.party-management-service.max-self-allocated-parties + ``` + By default the value of this parameter is 0. +- An IDP administrator can now only allocate parties confined to their own IDP perimeter. + +### New metrics related to LSU + +Some new metrics have been added to monitor the status of an LSU. + +- For sequencers: [daml.received-lsu-sequencing-test-messages](https://docs.digitalasset.com/operate/3.5/reference/metrics.html#daml-received-lsu-sequencing-test-messages) + + Allows to track the number of `LsuSequencingTest` messages received by a mediator, per sender. + +- For participants: [daml.participant.lsu_status](https://docs.digitalasset.com/operate/3.5/reference/metrics.html#daml-participant-lsu-status) + + Exposes the status of an LSU on a participant node. + +- For sequencers: [daml.sequencer.public-api.handshakes](https://docs.digitalasset.com/operate/3.5/reference/metrics.html#daml-sequencer-public-api-handshakes) + + Exposes the number of handshakes per member and status. + Can be used to track how many of the participant nodes already performed handshake with the successor. + +- For sequencers: [daml.sequencer.lsu_contact_successor_status](https://docs.digitalasset.com/operate/3.5/reference/metrics.html#daml-sequencer-lsu-contact-successor-status) + + Exposes the status of the handshake between a sequencer and its successor. + +## Performance Improvements + +### Session Signing Keys + +Session signing keys can now be used to reduce the number of calls to external KMS (Key Management Service) providers. When enabled, session signing keys are generated and cached locally for a limited duration and used for signing operations during their validity period. + +Please read the documentation on [Session Signing Keys](https://docs.digitalasset.com/operate/3.5/howtos/secure/keys/session_signing_keys.html) for details on how to enable and configure this feature. +Session signing keys are only available from Protocol Version 35 and are not enabled by default. + +### Compatible sibling views compression + +In protocol version 35, each envelope in `TransactionConfirmationRequest` contains multiple views grouped by recipients instead of one envelope per view. +Assignment and re-assignments also use this new format, but they always have one view. + +### Single Topology Transaction for External Parties + +Multiple topology transactions for external parties can now be represented with a single `PartyToParticipant` topology transaction. + +The `generateExternalPartyTopology` endpoint on the Ledger API now returns a single `PartyToParticipant` topology transaction to onboard the party. +The transaction contains signing threshold and signing keys. This effectively deprecate the usage of `PartyToKeyMapping`. +For parties with signing keys both in `PartyToParticipant` and `PartyToKeyMapping`, the keys from `PartyToParticipant` take precedence. + +Deprecated usage of `PartyToKeyMapping`. The functionality provided by `PartyToKeyMapping` is now available directly in `PartyToParticipant`. +Please use `PartyToParticipant` for new transactions. `PartyToKeyMapping` is still fully supported in this version (including existing and new transactions). +In future version, creation of new `PartyToKeyMapping` transactions may be disallowed. + +Deprecated `TopologyManagerReadService.ListAll` in favor of `ListAllV2`, which uses an inclusion +list (`include_mappings`) instead of an exclusion list (`exclude_mappings`). This avoids sending +mapping codes unknown to older servers. The console method `topology.transactions.list` now calls +`ListAllV2` by default and only falls back to `ListAll` when targeting a 3.4 node. The +`excludeMappings` and `protocolVersion` parameters of `topology.transactions.list` are deprecated; +use `filterMappings` instead. + +Deprecated `TopologyManagerReadService.ExportTopologySnapshot` and `TopologyManagerWriteService.ImportTopologySnapshot`, +along with their console counterparts `topology.transactions.export_topology_snapshot`, +`topology.transactions.import_topology_snapshot`, `topology.transactions.import_topology_snapshot_from`, +and `topology.transactions.export_identity_transactions`. +Please use the corresponding `V2` variants (`ExportTopologySnapshotV2` / `ImportTopologySnapshotV2`, +`export_topology_snapshotV2`, `import_topology_snapshotV2`, `import_topology_snapshot_fromV2`, +`export_identity_transactionsV2`) instead, which use an updated internal bytestring format. + +Deprecated `SequencerInitializationService.InitializeSequencerFromGenesisState`, +`SequencerInitializationService.InitializeSequencerFromOnboardingState`, +`SequencerAdministrationService.OnboardingState`, and +`TopologyManagerReadService.GenesisState`, along with their console counterparts +`setup.assign_from_genesis_state`, `setup.assign_from_onboarding_state`, +`setup.onboarding_state_for_sequencer`, `setup.onboarding_state_at_timestamp`, +and `topology.transactions.genesis_state`. +Please use the corresponding `V2` variants (`InitializeSequencerFromGenesisStateV2`, +`InitializeSequencerFromOnboardingStateV2`, `OnboardingStateV2`, `GenesisStateV2`, +`assign_from_genesis_stateV2`, `assign_from_onboarding_stateV2`, +`onboarding_state_for_sequencerV2`, `onboarding_state_at_timestampV2`, +`genesis_stateV2`) instead, which use an updated internal bytestring format +that enables streaming ingestion, making snapshot export and import significantly less memory-intensive. + +### Minor Performance Improvements + +- The Postgres connection tuning configuration of the indexer is now separated from the configuration of the Ledger API server + (`canton.participants..ledger-api.postgres-data-source`). + The new configuration section `canton.participants..parameters.ledger-api-server.indexer.postgres-data-source` should + be used instead to tune the indexer's Postgres connections. +- A new indexer pipeline batching strategy added under the feature flag `useWeighetdBatching`. When switched on, the + batches are created using their estimated database processing time using the `submissionBatchInsertionSize` as a limit + for individual batches +- Changed the `CompressedBatch` structure in the sequencer protocol for protocol version 35 to separately keep recipients and envelopes (from `gzip(Seq((recp1, payload1), (recp2, payload2)))` to `gzip(Seq(recp1, recp2)), Seq(gzip(payload1), gzip(payload2)))`). +- Batching configuration now allows setting different parallelism for pruning (currently only for Sequencer pruning): + New option `canton.sequencers.sequencer.parameters.batching.pruning-parallelism` (defaults to `2`) can be used + separately from the general `canton.sequencers.sequencer.parameters.batching.parallelism` setting. +- Made the config option `...topology.use-time-proofs-to-observe-effective-time` work and changed the default to `false`. + Disabling this option activates a more robust time advancement broadcast mechanism on the sequencers, + which however still does not tolerate crashes or big gaps in block sequencing times. The parameters can be configured + in the sequencer via `canton.sequencers..parameters.time-advancing-topology`. +- Additional metrics for the ACS commitment processor: `daml.participant.sync.commitments.last-incoming-received`, `daml.participant.sync.commitments.last-incoming-processed`, `daml.participant.sync.commitments.last-locally-completed`, and `daml.participant.sync.commitments.last-locally-checkpointed`. + +## Breaking Changes + +### Removal of legacy party replication repair console macros + +The original party replication method, which relied on a silent synchronizer, has been superseded by the offline party +replication process. Consequently, the obsolete repair console macros associated with the legacy approach have +been removed. + +Specifically, the following macros are no longer available: +- `step1_hold_and_store_acs` +- `step2_import_acs` + +If you previously relied on the _Silent synchronizer replication procedure_, you will need to transition to the +current offline party replication process. For details, please consult the +[Offline Party Replication documentation](https://docs.digitalasset.com/operate/3.5/howtos/operate/parties/party_replication.html#offline-party-replication) + +### Removal of deprecated, legacy ACS export and import endpoints + +The legacy repair endpoints for the ACS export and import have been removed: + +- Console command `participant.repair.export_acs_old` +- Console command `participant.repair.import_acs_old` +- gRPC rpc `ParticipantRepairService.ExportAcsOld` +- gRPC rpc `ParticipantRepairService.ImportAcsOld` + +#### Migration advice + +Use repair endpoints without the 'old' suffix: + +- Migrate to `participant.repair.export_acs` from `participant.repair.export_acs_old` +- Migrate to `participant.repair.import_acs` from `participant.repair.import_acs_old` +- Migrate to `ParticipantRepairService.ExportAcs` from `ParticipantRepairService.ExportAcsOld` +- Migrate to `ParticipantRepairService.ImportAcs` from `ParticipantRepairService.ImportAcsOld` + +Note that previously created ACS snapshots with the legacy endpoints cannot be imported with the current endpoints as +the underlying data format has completely changed. + +##### Migrating to export_acs + +The most significant change is the removal of the `timestamp` parameter, which has been replaced by a mandatory +`ledgerOffset` parameter. + +**Console parameter changes:** + +- **New mandatory parameter:** `ledgerOffset (Long)`. You must now specify the exact ledger offset for the snapshot + instead of a `timestamp`. +- **Removed parameters:** `partiesOffboarding`, `timestamp` (replaced by `ledgerOffset`), `force`. +- **Renamed parameters:** `outputFile` is now `exportFilePath` (default is `"canton-acs-export.gz"`), + `filterSynchronizerId` is now `synchronizerId`. +- **New optional parameters:** `excludedStakeholders` allows you to omit contracts that have one or more of these + parties as a stakeholder; `contractSynchronizerRenames` allows mapping contracts from one synchronizer to another + during export. + +**gRPC changes for `ExportAcsRequest`:** + +- **`parties` -> `party_ids`:** Field renamed for consistency. If left empty, the endpoint will act as a wildcard and + export the ACS for *all* parties hosted by the participant. +- **`timestamp` -> `ledger_offset` (Breaking):** You must provide an exact `int64 ledger_offset` instead of a timestamp. +- **`filter_synchronizer_id` -> `synchronizer_id`:** Field renamed for consistency. +- **Removed fields:** `force` and `parties_offboarding` have been completely removed. +- **New fields:** `contract_synchronizer_renames` and `excluded_stakeholder_ids`. + +##### Migrating to import_acs + +The import command remains largely the same in basic usage, but introduces new optional parameters for advanced +validation and overrides, alongside strict memory-efficient streaming semantics for gRPC. + +**Console parameter changes:** + +- **Renamed parameter:** `inputFile` is now `importFilePath` (default is `"canton-acs-export.gz"`). +- **New optional parameters:** `contractImportMode` governs contract validation upon import (defaults to + `ContractImportMode.Validation`); `representativePackageIdOverride` allows overriding representative package IDs + during import; `excludedStakeholders` allows omitting contracts that have one or more of these parties as a + stakeholder. + +**gRPC changes for `ImportAcsRequest`:** + +- **Streaming Semantics (Breaking):** The new endpoint requires metadata fields (like `contract_import_mode`, + `synchronizer_id`, etc.) to be populated *only* in the first request of the stream. Subsequent requests must omit + metadata and only contain the binary `acs_snapshot` chunks. +- **New mandatory fields:** `contract_import_mode` and `synchronizer_id` must be explicitly defined in the first stream + request. +- **Removed fields:** `allow_contract_id_suffix_recomputation` is completely removed. +- **New fields:** `excluded_stakeholder_ids` and `representative_package_id_override`. +- **Response update:** `ImportAcsResponse` is now a completely empty message (previously returned a contract ID + mapping). + +### Only PackageName is accepted on Ledger API + +Usage of package id for ledger queries was deprecated and now the validation will fail if used. +The impacted APIs are: + - GetUpdates + - GetUpdateByOffset + - GetUpdateById + - GetActiveContracts + - GetEventsByContractIdRequest + - SubmitAndWaitForTransaction (the optional `transaction_format`) + - SubmitAndWaitForReassignmentRequest + - ExecuteSubmissionAndWaitForTransactionRequest + +### SynchronizerId field update in Externally signed transactions + +In Protocol version 35, the `synchronizer_id` field in externally signed prepared transaction metadata +will be populated with the physical synchronizer ID of the synchronizer on which the transaction will be processed, +instead of the logical synchronizer ID, as is the case in PV 34. +Applications must ensure they do not rely on the format of the `synchronizer_id` value. + +### Changes from NonNegativeLong to Long + +Some console commands using a NonNegativeLong for the offset are changed to accept a Long instead. +Similarly, some console commands returning an offset now return a Long instead of a NonNegativeLong. +It brings consistency and allows to pass the output of `participant.ledger_api.state.end()`. + +Impacted commands: +- `participant.repair.export_acs` +- `participant.parties.find_party_max_activation_offset` +- `participant.parties.find_party_max_deactivation_offset` +- `participant.parties.find_highest_offset_by_timestamp` + +### Removal of automatic recomputation of contract ids upon ACS import + +The ability to recompute contract ids upon ACS import has been removed. + +### Removal of multi-host name resolution tooling + +Support for the multi-host name resolution was removed. +This was only used if synchronizer connectivity defined a sequencer with multiple endpoints, which is not supported with our current sequencers: +we now have multiple sequencers each with exactly one endpoint. + +### Ledger JSON API Spec Corrections + +JSON Ledger API OpenAPI/AsyncAPI spec corrections +- Fields not marked as required in the Ledger API `.proto` specification are now also optional in the OpenAPI/AsyncAPI specifications. + If your client code is using code generated using previous versions of these specifications, it may not compile or function correctly with the new version. To migrate: + - If you prefer not to update your code, continue using the previous specification versions as the JSON API server preserves backward compatibility. + - If you want to use new endpoints, features or leverage the new less strict spec, migrate to the new OpenAPI/AsyncAPI specifications as follows: + - Java clients: No changes are needed if you use the `OpenAPI Generator`. Otherwise, potentially optionality of fields should be handled appropriately for other code generators. + - TypeScript clients: Update your code to handle optional fields, using the `!` or `??` operators as appropriate. +- From Canton 3.5 onwards, OpenAPI/AsyncAPI specification files are suffixed with the Canton version (e.g., `openapi-3.5.0.yaml`). +- Canton 3.5 is compatible with OpenAPI specification files from version 3.4.0 to 3.5.0 (inclusive). + +- The Ledger JSON API server now enforces that only fields marked as required by the Ledger API OpenAPI/AsyncAPI specification are mandatory in request payloads. + +### Change from grpcurl to grpc-health-probe in all Docker images + +The tool used for health check probes changed from grpcurl to grpc-health-probe in all the docker images. + +### Minor Breaking Changes + +- The expert `keep-alive-client` configuration parameter for various client services moved to `channel.keep-alive-client`. +- We reduced the defaults for `setBalanceRequestSubmissionWindowSize` and `defaultMaxSequencingTimeOffset` + to 2 minutes. +- The default OTLP gRPC port that the Canton connects to in order to export the traces has been changed from + 4318 to 4317. This aligns the default configuration of Canton with the default configuration of the OpenTelemetry + Collector. This change affects only the users who have configured an OTLP trace export through + ``` + canton.monitoring.tracing.tracer.exporter.type=otlp + ``` +- Removed the `LastErrorsAppender` along with the Admin API endpoints `StatusService.GetLastErrors` and `StatusServiceGetLastErrorTrace`, as + well as the corresponding console commands `last_errors` and `last_error_trace`. + + +## Deprecations + +### Deprecate scope-based access tokens +- "Scope-based" access tokens, i.e. JWTs without any audience specified, have been deprecated. +- A configuration that does not specify a `target-audience` will log a warning on node startup. +- Configurations that specify both a `target-audience` and a `target-scope` are not supported in this version and will also log a warning on node startup. +- Starting Canton version 3.7, support for "scope-based" tokens will be removed entirely to enforce a valid `aud` field in every incoming JWT. +- The `scope` field will, in a future version, be repurposed to serve exclusively as an additional, optional claim for fine-grained permissions. + +### Removal of the old sequencer connection transports + +The old sequencer connections transports have been removed, and only the new sequencer connection pool remains. +Consequently, the configuration `.sequencer-client.use-new-connection-pool` has been deprecated and no longer has any effect. + +### Deprecate initial protocol version configuration + +The config key `participant.parameters.initial-protocol-version` was unused and has been marked as deprecated. + +### Configuration Deprecations + +- The configuration parameters `topology.use-new-processor` and `topology.use-new-client` have been deprecated and now default to true. Configuring those parameters to false will be ignored. +- The parameter `canton.participants..parameters.package-metadata-view.init-takes-too-long-interval` + is now ignored, and a warning will only be printed once, rather than periodically. +- The parameter `canton.participants..parameters.ledger-api-server.indexer.prepare-package-metadata-time-out-warning` + is now ignored. +- The individual JVM metric flags `classes`, `cpu`, `memoryPools`, `threads`, `gc`, and `buffers` in + `canton.monitoring.metrics.jvm-metrics` are no longer supported since the upgrade to OpenTelemetry instrumentation 2.26.0. + All standard JVM metrics (classes, cpu, memory pools, threads, garbage collector) are now always enabled when + `jvm-metrics.enabled = true`. A new `experimental` flag has been added to control experimental JVM metrics + (e.g. buffer pools). Users who previously set `buffers = true` should migrate to `experimental = true`. + See https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/16087 for details. +- The Zipkin trace exporter configuration `canton.monitoring.tracing.tracer.exporter.type=zipkin` is + deprecated following the OpenTelemetry specification deprecation of Zipkin exporters. The Zipkin exporter + will be removed in a future release. Users should migrate to the OTLP exporter. + See https://opentelemetry.io/blog/2025/deprecating-zipkin-exporters/ for details. +- Removed the feature flag `canton.sequencers..parameters.async-writer.enabled`, as async writing is now + the only supported mode. +- Changed the path for `crypto.kms.session-signing-keys` (deprecated) to `crypto.session-signing-keys` so that session signing key configuration is no longer directly tied to a KMS. However, session signing keys can still only be enabled when using a KMS provider or when running with `non-standard-config=true`. +- `package-dependency-cache` field in `caching` configuration is deprecated. It can be removed safely from node configurations. + +### Ledger JSON API package vetting endpoints + +The Ledger JSON API `v2/package-vetting` endpoint exposes list functionality on the GET method by accepting a request body. This is not recommended by the HTTP specification, hence the endpoint is deprecated. +For consistency, the POST method, used for updating the vetting state, of the same endpoint is also deprecated. + +In turn, two new endpoints are implemented to provide the same functionality: +- `v2/package-vetting/list` accepts a POST request with the same body as the deprecated GET `v2/package-vetting` endpoint and returns the list of vetted packages in the same format. +- `v2/package-vetting/update` accepts a POST request with the same body as the deprecated POST endpoint `v2/package-vetting` and returns the updated vetting state of the package in the same format. + + +### Protocol version parameter in topology list commands + +The `protocolVersion` parameter in all `.topology..list` console commands has been deprecated and will be removed in a future version. + +## Minor Improvements + +### Bugfixes + +- Fixed a mid-crash recovery issue for offline party replication and repair ACS imports. Previously, if an ACS import + was interrupted (for example by a participant node restart or crash), a subsequent recovery attempt could result in + missing contracts on the Ledger API. The recovery process now properly rolls back uncommitted partial states upon + retrying the ACS import, ensuring recovered contracts are completely synchronized across both internal storage + and the Ledger API. +- Fixed a bug where the Ledger API `PackageService.ListVettedPackages` used to return a potentially not yet + effective state of the vetted packages. Now it returns the state of vetted packages effective at the time of the request. +- Sequencer health status used to incorrectly return the synchronizer uid instead of the sequencer uid. +- Prevent Ledger API crashes after running `ParticipantRepairService.PurgeContracts` admin command. + Fixes a critical issue where using the `ParticipantRepairService.PurgeContracts` command (when multi-synchronizer support is disabled) generated malformed + Daml values for the choice argument and choice result of the `Archive` choice of the purge contract events in the Ledger API event store. + This previously caused the Ledger API streams reading the generated `Archive` events to crash. + The repair command now generates correct Daml values for the corresponding entries, that can be safely delivered by the Ledger API. +- Fixed a bug in the repair service's `changeAssignation` where only a single repair counter was allocated when reassigning multiple contracts, + violating the monotonicity expected by the indexer. + +### Ledger API Multi-Synchronizer Events Alpha Support + +Adds a new participant node parameter, `alpha-multi-synchronizer-support` (Boolean). +- **Default (`false`):** Uses standard **Create** and **Archive** events. +- **Enabled (`true`):** Uses **Assign** and **Unassign** events. + +This flag is required in multi-synchronizer environments to preserve the **reassignment counter** of a contract. +Using the default (Create events) resets this counter to zero. + +Note: Multi-synchronizer support is currently in Alpha; most Ledger API consumers may not yet be compatible with +Assign/Unassign events. Only enable this if your application specifically requires non-zero reassignment counters +and can process these event types. + +### Support for adding table settings for PostgreSQL + +Added support for adding table settings for PostgreSQL. One can use a repeatable migration (Flyway feature) in a file +provided to Canton externally. + - Use the new config `repeatable-migrations-paths` under the `canton...storage.parameters` configuration section. + - The config takes a list of directories where repeatable migration files must be placed, paths must be prefixed with `filesystem:` for Flyway to recognize them. + - Example: `canton.sequencers.sequencer1.storage.parameters.repeatable-migrations-paths = ["filesystem:community/common/src/test/resources/test_table_settings"]`. + - Only repeatable migrations are allowed in these directories: files with names starting with `R__` and ending with `.sql`. + - The files cannot be removed once added, but they can be modified (unlike the `V__` versioned schema migrations), and if modified these will be reapplied on each Canton startup. + - The files are applied in lexicographical order. + - Example use case: adding `autovacuum_*` settings to existing tables. + - Only add idempotent changes in repeatable migrations. + +### Offline root namespace key scripts + +Offline root namespace key scripts: +- Renamed `prepare-certs.sh` to `prepare-cert.sh` +- Changed `assemble-certs.sh` to automatically suffix the generated certificate with a `.cert` extension, similarly to what is being done in `prepare-cert.sh` +- Removed the `10-offline-root-namespace-init` example folder as its content is now integrated in the documented how-to: https://docs.digitalasset.com/operate/3.5/howtos/secure/keys/namespace_key.html +- Committed the buf image necessary to run the script to the repository (also available in the release artifact), making usage from the open source repo easier + +### Reliability Improvements + +- Added a field `MaxConcurrentCallsPerConnection` and corresponding default + `defaultMaxConcurrentCallsPerConnection` (set to 100000) to `ServerConfig`. + This corresponds to `max-concurrent-streams-per-connection` in the app configs, e.g., + `docker/canton/images/canton-sequencer/app.conf` and can be changed there. At present + the value for sequencers is configured to be 500 for the public API and 100 for the Admin API. +- Added network timeout and client_connection_check_interval for db operations in the Ledger API server and indexer to avoid + hanging connections for Postgres (see PostgresDataSourceConfig). The defaults are 60 seconds network timeout and + 5 seconds client_connection_check_interval for the Ledger API server, and 20 seconds network timeout and + 5 seconds client_connection_check_interval for the indexer. These values can be configured via the new configuration parameters + `canton.participants..ledger-api.postgres-data-source.network-timeout` for network timeout of the Ledger API + server and `canton.participants..parameters.ledger-api-server.indexer.postgres-data-source.client-connection-check-interval` + for the client_connection_check_interval of the indexer. +- `.replication.connection-pool.connection.client-connection-check-interval` is introduced + that allows configuring the PostgreSQL-specific `client_connection_check_interval` parameter for DB locked connections. + This is a safety mechanism to prevent hanging connections in case of network issues. The default value is 5 seconds. +- The Ledger API now enforces a maximum number of signatures per party that can be provided for external submissions. + This value defaults to 50 and can be changed at the following config path: `canton.participants..ledger-api.interactive-submission-service.maximum-number-of-signatures-per-party` +- Added a new configuration parameter `canton.participants..ledger-api.index-service.max-lookup-limit` that caps the maximum number of contracts returned by a contract key lookup per request. + The default value is 1000. +- When the AcsCommitmentProcessor is initializing, read stakeholder groups from the snapshot in batches of size + `canton.parameters.general.batching.max-stakeholder-groups-batch-size` (default 1000), rather than all at once. + This allows early termination of this initialization if the node is shutting down. +- The release version is now exposed in `NodeStatus.NotInitialized`, so the node version can be retrieved even before the node is initialized. + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.10+7-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.9 (Debian 17.9-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-2.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-2.mdx new file mode 100644 index 000000000..bca1fc7a7 --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-2.mdx @@ -0,0 +1,74 @@ +--- +title: "3.5.2" +description: "Canton 3.5.2 release notes." +--- + +# Release of Canton 3.5.2 + +Canton 3.5.2 has been released on June 03, 2026. + +## Summary + +This is a maintenance release that fixes an out of memory issue during synchronizer reconnect, addresses security vulnerabilities in the Canton docker base image, as well as minor improvements. + +## Minor Improvements + +- Updated the Docker base image to 1.0.8 to address vulnerabilities in Busybox: CVE-2025-46394, CVE-2025-60876. +- Fix sequencer subscription closing errors `Timeout 9 seconds expired, but readers are still active in Task closing flushing direct-sequencer-subscription`. +- Handle decoding of `ContractId Void` in PQS +- Improve caching of contract key lookups + +## Deprecations + +### Removal of synchronous processing on the mediator +The synchronous processing mode on the mediator has been removed in favor of the asynchronous one +that offers better performance. + +Config path `myMediator.config.asynchronous-processing` has been deprecated as it is on by default now. + +## Bugfixes + +### (26-002, Medium): OOM from high memory usage after disconnecting and reconnecting from/to a synchronizer + +#### Issue Description +Because of the periodic time proof retrieval task (with interval minObservationDuration), the ConnectedSynchronizer +instance cannot be garbage collected right away after a disconnect/reconnect from the synchronizer. + +This leads to higher memory consumption which can lead to OOMs. + +#### Affected Deployments +Participant nodes + +#### Affected Versions +All versions before 3.5.2 + +#### Impact +High memory consumption and potentially OOM of the participant + +#### Symptom +The heap dump shows instances of the ConnectedSynchronizer from previously disconnected synchronizers. +The stack of calls refers to PeriodicAcknowledgements, TimeProofRequestsSubmitterImpl, SynchronizerTimeTracker and finally Clock + +#### Workaround +Restart the participant. + +#### Likeliness +Sometimes + +#### Recommendation +Upgrade to 3.5.2 + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.12+2-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.10 (Debian 17.10-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-3.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-3.mdx new file mode 100644 index 000000000..ef3704462 --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-3.mdx @@ -0,0 +1,34 @@ +--- +title: "3.5.3" +description: "Canton 3.5.3 release notes." +--- + +# Release of Canton 3.5.3 + +Canton 3.5.3 has been released on June 03, 2026. + +## Summary + +This is a maintenance release that fixes an issue around mediator restart and LSU. + +## Bugfixes + +### Mediator cannot reconnect to sequencer after processing LSU sequencing test message + +During LSU, after processing a sequencing test message, a mediator cannot reconnect to the sequencer. +The connection issue resolves itself when the upgrade time is reached and traffic is initialized on the sequencer. + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.12+2-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.10 (Debian 17.10-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-4.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-4.mdx new file mode 100644 index 000000000..ad6fe9060 --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-4.mdx @@ -0,0 +1,55 @@ +--- +title: "3.5.4" +description: "Canton 3.5.4 release notes." +--- + +# Release of Canton 3.5.4 + +Canton 3.5.4 has been released on June 10, 2026. + +## Summary + +This is a maintenance release that fixes a bug around modify synchronizers and brings a few improvements. + +## What’s New + +### Minor Improvements +- Config flag `canton.participants.participant.parameter.alpha-multi-synchronizer-support` was renamed to `enable-all-ledger-api-reassignments`. +- Participant topology feature flag `EnableAlphaMultiSynchronizer` was renamed to `EnableMultiSynchronizer`. +- Improved coordination between the ACS commitment processor and its store, preventing benign `DB_STORAGE_DEGRADATION` warnings during participant shutdown. + +#### Improvements around listing synchronizers + +- Message `ListRegisteredSynchronizersRequest` accepts a new `all_statuses` attribute to allow to retrieve all + registered synchronizers (and not only the active ones). +- Added new console command `participant1.synchronizers.list_all_registered` to list all registered synchronizers. + +#### Improvements around BFT Orderer's database pruning and performance + +- Database tables partitioning was introduced to the database tables of the BFT Orderer +- These changes bring significant improvements to the performance of the pruning operation as well as many other queries that got optimized +- IMPORTANT: these changes require running a new automatic database migration that can potentially take considerable amount of time, depending on how much data is in the affected tables at the time of the migration + +## Bugfixes + +- Fixed an issue that was making `ModifySynchronizer` request to fail if the physical synchronizer id was set. + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.12+2-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.10 (Debian 17.10-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | + + +## What's Coming + +We are currently working on diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-5.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-5.mdx new file mode 100644 index 000000000..376392536 --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-5.mdx @@ -0,0 +1,72 @@ +--- +title: "3.5.5" +description: "Canton 3.5.5 release notes." +--- + +# Release of Canton 3.5.5 + +Canton 3.5.5 has been released on June 17, 2026. + +## Summary + +This is a maintenance release that fixes an exceptional but severe bug around logical synchronizer upgrades and brings a few improvements. + +## What’s New + +### Dependencies of vetted packages can be unvetted (PV35+) +On synchronizers running protocol versions 35 and above, vetting state change operations on the API (e.g. via ``/package-vetting/update``) +allow dependencies of vetted packages to be unvetted safely, without requiring the use of a force flag. +This is consistent with the transaction protocol, which does not require the dependencies of a Daml transaction node package to be vetted in PV35+. +**Note**: For protocol versions 34 and below, the package dependency vetting restrictions remain unchanged. + +### Minor Improvements + +- Updated docker base image from 1.0.8 to 1.0.9, which bumps grpc-health-probe to v0.4.52. +- Reduce memory footprint with streaming of large gRPC responses by avoiding buffering the whole response. +- Improve log message in the sequencer in case queuing the event signal is not possible due to a closed subscription and not describe that case as an error. +- New participant config `canton.participants.myParticipant.parameters.connect-to-synchronizers-on-startup` allows to disable automatic connect to synchronizers + on startup. + +## Bugfixes + +### (26-003, High): Restarting stuck sequencer yields to sequencer fork + +#### Issue Description +Around LSU: if a sequencer not processing blocks is restarted after upgrade time and before it had the chance to process any block, +it will delete traffic data entries as part of crash recovery. + +#### Affected Deployments +Sequencer nodes + +#### Affected Versions +All versions before 3.5.5 + +#### Impact +Sequencer fork + +#### Symptom +Inability for participants to connect (cannot get consensus on traffic) or participant disconnecting with SEQUENCER_FORK_DETECTED + +#### Workaround +Restore from backup + +#### Likeliness +Exceptional + +#### Recommendation +Upgrade to 3.5.5 + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.12+2-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.10 (Debian 17.10-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | diff --git a/docs-main/global-synchronizer/release-notes/canton-releases/3-5-6.mdx b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-6.mdx new file mode 100644 index 000000000..53436aa8f --- /dev/null +++ b/docs-main/global-synchronizer/release-notes/canton-releases/3-5-6.mdx @@ -0,0 +1,82 @@ +--- +title: "3.5.6" +description: "Canton 3.5.6 release notes." +--- + +# Release of Canton 3.5.6 + +Canton 3.5.6 has been released on June 24, 2026. + +## Summary + +This is a maintenance release that fixes an exceptional severe bug around logical synchronizer upgrades and brings a few improvements. + +## What’s New + +### Minor Improvements +- Connection pool metrics: + - Add a `psid` label, populated if it is provided when connecting. This should be the case starting from the second connection to a synchronizer, or upon LSU. + - Close the `connection-health` and `subscription-health` metrics associated to the `psid` when the pool is closed, instead of closing all the existing ones when the pool is started. +- Updated com.google.protobuf libs from 3.25.5 --> 3.25.9 +- LSU: A call to `AcknowledgeSigned` with a timestamp before the upgrade time returns immediately, without any acknowledgement being done. +- Fix JDBC query for computing last activations: Under adverse conditions (data corruption) this query might called with empty inputs where it would have failed execution with PostgreSQL server version 14. +- (Potentially) *BREAKING*: Aggregatable submissions are now rejected eagerly to preserve bandwidth. + This means that the submission error code `SEQUENCER_AGGREGATE_SUBMISSION_ALREADY_SENT` may now also + be returned during the synchronous submission of the sequencer, as the state of the aggregation is also + checked before ordering. In addition, the gRPC error code has been modified from `FAILED_PRECONDITION` to + `ALREADY_EXISTS` to better reflect the nature of the error. Clients should be updated to handle this error + code accordingly. Due to backwards compatibility, the old gRPC error code will be returned for PV35 and + before on the async path, and the new capability must only be turned on when all nodes have been + upgraded to a Canton version that supports this change. The new capability can be disabled using `canton.sequencers.seq.parameters.enable-reject-delivered-aggregations-on-pv-35 = []` + for mediators. This can be combined with the new configuration option of the mediator `canton.mediators.mymediator.parameters.delayed-verdict-sender.enabled = false`. + Generally, the sequencer will send out the verdict after reaching the threshold. All subsequent sent verdicts are thrown away. The new option now allows threshold + extra verdicts to be sent immediately, while the rest of the mediators will wait a short amount of time. This allows to reduce the load on the sequencer by 30%, creating more capacity for other transactions. +- Fixed an issue that prevent external parties from being allocated on a participant with an offline root key via the Ledger API's `PartyManagementService.AllocateExternalParty` endpoint. +- Fixed an issue whereby the sequencer would no longer make progress after a failed write. Sequencers in such a state now report 'liveness' unhealthy. +- Require Participant Admin (not just IDP Admin) permissions in order to be able to grant CanReadAsAnyParty and CanExecuteAsAnyParty rights to users. + +## Bugfixes + +### (26-004, High): LSU: Missing synchronization between topology local copy and purging + +#### Issue Description +Because of the lack of synchronization, it can happen that topology purging kicks in before the local copy of the topology state is finished, which result in incorrect topology state for the successor. + +The issue can occur only when topology purging is enabled, which is not the case by default. + +#### Affected Deployments +Participant nodes + +#### Affected Versions +All versions before 3.5.6 + +#### Impact +Topology fork + +#### Symptom + +- Participant nodes issues warning/errors about missing topology transactions that were valid before the LSU. +- Submission of a transaction is rejected because of missing topology transactions that were valid before the LSU. + +#### Workaround +Restore from backup and ensure topology purging is disabled + +#### Likeliness +Exceptional + +#### Recommendation +Upgrade to 3.5.6 + +## Compatibility + +The following Canton protocol versions are supported: + +| Dependency | Version | +|----------------------------|----------------------------| +| Canton protocol versions | 34, 35 | + +Canton has been tested against the following versions of its dependencies: + +| Dependency | Version | +|----------------------------|----------------------------| +| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.12+2-nixos, mixed mode, sharing) | +| Postgres | Recommended: PostgreSQL 17.10 (Debian 17.10-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | diff --git a/docs-main/global-synchronizer/release-notes/canton.mdx b/docs-main/global-synchronizer/release-notes/canton.mdx index 2298af22a..9748ccaa6 100644 --- a/docs-main/global-synchronizer/release-notes/canton.mdx +++ b/docs-main/global-synchronizer/release-notes/canton.mdx @@ -1,914 +1,17 @@ --- title: "Canton" -description: "Release notes for Canton tools, including PQS, Daml Shell, Daml language, and more." +description: "Release notes for Canton." --- -The latest stable Canton release is reproduced below verbatim from the upstream release notes. For the full Canton release history, including older versions, see the [Canton release notes on the Digital Asset blog](https://blog.digitalasset.com/developers/release-notes/author/curtis-hrischuk). +{/* Generated from upstream digital-asset/canton release-note sources. */} +Canton release notes are reproduced below from the upstream `digital-asset/canton` release-note sources. -# Release of Canton 3.5.3 +## Releases - -## Introduction - -The Canton 3.5.3 release notes for Splice 0.6.0 are provided below. The content here focuses on Canton updates only. For Splice related changes, please refer to the [Splice Release Notes](https://docs.canton.network/global-synchronizer/release-notes/splice). - -These notes are broken down into the key sections below: - -* Application Development and Tooling -* Ledger API -* Admin API and Console -* Deployment and Configuration -* Operational Procedures - -dApp developers will be interested in the top three while validator (or SuperValidator) operators are interested in the bottom three. - -Each section has three sub-sections that are self explanatory: - -* New Features and Enhancements -* Important Changes -* New Deprecations - -It is recommended to read all of the "Important Changes" sections to avoid missing an important update. - -Please note that the Canton 3.5 binary can operate like Canton 3.4 when the Protocol Version (PV) is set to 34 and the Daml-LF version is 2.2. The new features described below are enabled when the PV is updated to 35 and, in some cases, Daml-LF to 2.3. This document describes the latter case, when PV is 35 and Daml-LF is 2.3. Note that to make use of DamlLF 2.3 features, a recompilation of the dApp may be needed. - -Although much of the discussion is applicable to the Canton Network or Splice, many of the improvements will be relevant to application developers for private synchronizer or multi-synchronizer applications. - - -## Application Development and Tooling - -### New Features and Enhancements - -#### Introducing Contract Keys - -This release introduces the contract key feature which was previously available in Daml 2.x. -A *contract key* is a stable identifier that simplifies reasoning and programming with the UTXO contracts. They make it easier to track contract IDs as the contract evolves: as a contract is updated, via archive and create operations, the currently active contract(s) can easily be referenced via the contract key. The contract key feature simplifies both the Daml business logic developer as well as the client of that Daml business logic (e.g., backend service). - -Contract keys will enable Ethereum or Solana developers to more easily develop applications on the Canton Network because the UTXO mental model is simplified. Services that integrate with the ledger will be simpler because they have a stable identifier to program. - -Contract keys are similar in concept to primary or secondary keys in relational databases. Contract keys do not change and can be used to refer to a contract even when the contract ID changes. Technically, the contract key can be any value that does not contain contract IDs. - -In this release, a contract key can refer to zero, one, or several contracts at the same time. In Canton 3.x, allowing a key to refer to one, zero, or several contracts does have the advantage that a contract key can act like a secondary key of a database. This version of contract keys assumes that key uniqueness is provided by the dApp or other external enforcement mechanisms. - -##### Daml Language Primitives - -The Daml language and compiler are reintroducing the two keywords: `key` and `maintainer`. To define a contract key in a Daml template, you use the `key` keyword and specify the `maintainer`. - -* The `key` expression identifies the contract (always includes a `Party` for scoping). -* A `maintainer` is the party that validates all action on a key to guarantee their consistency. The central task of the `maintainer` is to verify the keys are retrieved in a consistent order within the transaction. A `maintainer` must be a signatory. The maintainer must be expressed in terms of the key, which is available via the `key` identifier in the maintainer expression. - -Here’s an example of setting up a contract key for a bank account, to act as a bank account ID: - -```` -type AccountKey = (Party, Text) - -template Account with - bank : Party - number : Text - owner : Party - balance : Decimal - observers : [Party] - subaccount: optional AccountKey - where - signatory [bank, owner] - observer observers - - key (bank, number) : AccountKey - maintainer key._1 -```` - -Daml language now supports several primitives associated with contract keys. The following primitives are available: - -- `lookupByKey` \- It checks whether a contract with the given key exists and if yes, returns the contract id. If multiple contracts exist, the first one according to the lookup order below is returned. -- `fetchByKey` \- It fetches the first contract id and contract data associated with the given contract key. If multiple contracts exist, the first one according to the lookup order below is returned. -- `exerciseByKey` \- Exercise a choice on the first contract associated with the given key according to the order below. -- `lookupNByKey` \- Available in `DA.ContractKeys`. It looks up up to `n` contracts associated with the passed key, sorted according to the order below. - - In all cases, the contracts are returned in the following order: - -- First the contracts created within a transaction, starting with the most recent, -- Then explicitly disclosed contracts, -- Then contracts known to the participant in recency order. - -##### Daml Script Functions - -There are Daml Script functions \- counterparts of the standard library primitives: - -- `queryByKey` \- It looks up a contract associated with the passed key and returns its ids and data. It is of type `Script`, which means it must appear as top-level instruction as part of a Script. -- `queryNByKey` \- It looks up up to `n` contracts associated with the passed key and returns their ids and data. It is of type `Script`, which means it must appear as top-level instruction as part of a Script. -- `exerciseByKeyCmd` \- It exercises a choice on the first contract with the given key. It is of type `Commands` and must therefore be wrapped by a submit operation, and can be combined with other `Commands`. - -##### Smart Contract Upgrade (SCU) - -To support SCU upgrade for contract `key` and `maintainer` definitions, new guidelines have been added. Simply, the `key` and `maintainer` values are not allowed to be changed. At upgrade time, the recomputed key and maintainers are verified to be identical to the upgraded contract’s original key and maintainers. If they aren't, an upgrade error is raised and the transaction is aborted. It is forbidden to add or remove a key definition from a template in a later version of that template. The default is to enforce this at package vetting time. - -### Important Changes - -#### Daml-LF 2.3 - -A new version of Daml-LF is released: Daml-LF 2.3. To benefit from new features (like Contract Keys) this LF version enables, the package needs to be recompiled and its semantic version must be bumped to form a new element in the package lineage. (Please note that this will cause the package ID to change.) - -You can target Daml-LF 2.3 by setting the `--target=2.3`, either as direct argument on the command line or as part of a `daml.yaml`: - -```` -sdk-version: 3.5.1 -name: some-name -source: daml -version: 0.0.2 # updated from 0.0.1 -dependencies: - - daml-prim - - daml-stdlib -build-options: - - --target=2.3 -```` - -#### `dpm` Replaces the Daml Assistant (`daml`) CLI - -`dpm` is a command-line tool that allows users to run the SDK components. It is a drop-in replacement for the Daml Assistant (`daml`), which is removed in this release. It has an extensible plug-in architecture(see [Publishing Components](https://docs.canton.network/sdks-tools/cli-tools/dpm#publishing-components)). The Daml Assistant (daml) was [deprecated in Canton 3.4](https://blog.digitalasset.com/developers/release-notes/canton-3.4-release-notes-for-splice-0.5.0#changes-daml-language-and-ide). - -The `dpm` CLI is documented [here](https://docs.canton.network/sdks-tools/cli-tools/dpm#dpm-daml-package-manager). A command migration table, from `daml` to `dpm` commands, is available [here](https://docs.canton.network/sdks-tools/cli-tools/dpm#command-migration-table). - -#### Daml Exception Handling Updates - -Daml exception handling has been [deprecated since Canton 3.3](https://blog.digitalasset.com/developers/release-notes/splice-0.4.0-canton-3.3#a_use_error_description). deprecated. It is not removed in this release but there is a change: - -* Protocol version 35 does not support transactions which roll back write effects. - -User exceptions can still be thrown and caught. However, if a consuming choice is exercised or a contract is created within the try-catch block before the exception is caught, the entire command will fail and abort. The mitigation is to remove Daml exceptions and to recompile your Daml code. - -### New Deprecations - -#### Use PQS's `prune_archived_to_offset` instead of `prune_to_offset` - -The PQS `prune_to_offset` SQL function is deprecated. You should not use it anymore as it can introduce a deadlock with accompanying poor performance. The mitigation is to use the newly introduced `prune_archived_to_offset` as a replacement: It is non-blocking and 10x faster. - -## Ledger API - -### New Features and Enhancements - -#### Ledger API Contract Key Support - -The following contract-key related extensions have been made to the Ledger API - -- `contract_key_hash` has been added to the `CreatedEvent` message returned in the `State-` and `UpdateService` responses -- `prefetch_contract_keys` field present in the `Command` and `PrepareSubmissionRequest` used by the `Command-` `CommandSubmission-` and `InteractiveSubmissionService` are available to allow the caller to request prefetching the contract key cache underpinning the command interpretation. Use it when performance tests indicate that many sequential contract key lookups adversely impact the command interpretation speed. - -#### PQS Contract Key Support - -In PQS, contract keys are mere metadata that can be queried like any other metadata. It is possible to query for all contracts with a given key: - -```` -select contract_id, payload ->> 'label' -from __contracts -where contract_key = jsonb_build_object(...) -order by created_at_ix -```` - -#### Party Replication Topology Events To Signal Begin and End of Replication - -The `PartyToParticipant` topology "onboarding" state used in the process of replicating a party with existing contracts is now visible via the Ledger API, when a party onboards on a synchronizer on protocol version 35 or higher. Starting with PV=35, the newly introduced `ParticipantAuthorizationOnboarding` Ledger API topology event signals the beginning of party replication and transitions to `ParticipantAuthorizationAdded` once the party's ACS is fully visible on the Ledger API. - -#### New Transaction Hashing Scheme v3 for `InteractiveSubmissionService` - -The Ledger API prepare `InteractiveSubmissionService` has been modified to take in a specific hashing scheme version in the request. The default hashing scheme is `HASHING_SCHEME_VERSION_V2`. Integrators are encouraged to move to `HASHING_SCHEME_VERSION_V3` for synchronizers using protocol version 35\. **In particular, usage of contract keys requires `HASHING_SCHEME_VERSION_V3`.** - -- A new hashing scheme version `HASHING_SCHEME_VERSION_V3` has been introduced that includes the transaction's `max_record_time` in the hash computation and covers the new transaction node and fields of contract keys. This new version is available from Protocol Version 35\. -- See the [hashing algorithm documentation](https://docs.canton.network/appdev/deep-dives/external-signing-hashing-algorithm#hashing-scheme-version) for the updated version. -- The `max_record_time` is now enforced by all confirming participants. - -See the versioning [documentation](https://docs.canton.network/appdev/deep-dives/external-signing-hashing-algorithm#versioning) for more details. - -#### LAPI ACS Stream Enhancements - -The `GetActiveContracts` stream request has been extended with an optional `stream_continuation_token` field that allows clients to continue an interrupted ACS stream from the last element which made it through. The field can be populated with the `stream_continuation_token` field of the last response element received before the interruption, and the stream will continue from the next element after that. - -A new `GetActiveContractsPage` endpoint added to the State Service API. This enables the client to retrieve the ACS in paginated form, by specifying a `max_page_size`. The pages can be accessed sequentially by using the `page_token` field. The token can be obtained from the `GetActiveContractsPageResponse` of the last page. - -#### GetUpdates Stream Enhancements - -The `GetUpdatesRequest` object has a new optional parameter `descending_order`. When this parameter is `true` the events are streamed from the newest to the oldest ones. The pages can be accessed sequentially by using the `page_token` field. An example use for this feature is to view the transaction history from newest to oldest. - -A new `GetUpdatesPage` endpoint has been added to the Update Service API that supports pagination. This allows retrieval of updates in paginated form instead of requesting the stream. - -#### Optimizing ACS Queries using Active Contracts Head Snapshot (ACHS) - -The Active Contracts Head Snapshot (ACHS) is a new optional feature that maintains a continuously updated snapshot of the currently active contracts. When enabled, the ACHS accelerates `GetActiveContracts` (ACS) queries by allowing them to read directly from a pre-computed snapshot rather than scanning the full event log to reconstruct the active set. - -ACHS is disabled by default. To enable it, configure the `achs-config` block under the participant's indexer settings: - -``` -canton.participants..parameters.ledger-api-server.indexer.achs-config { - valid-at-distance-target = 1000000 - last-populated-distance-target = 500000 -} -``` - -The `valid-at-distance-target` controls how far behind the ledger end (in event sequential IDs) the snapshot's validity point is maintained. The ACHS is not used for serving queries below its validity point, logging at INFO level `ACHS for (...) skipped since validAt (...) already surpassed requested activeAt (...)`. If the `valid-at-distance-target` value is too small, long-running ACS queries may observe the ACHS validity point moving (mid-stream) past their requested offset, causing the stream to fall back to the slower filter tables query, logging at INFO level `ACHS stream for (...) fell back to filter tables from (...) since validAt (...) surpassed activeAtEventSeqId (...)`. If the value is too large, the tail portion of the ACS (between the ACHS validity point and the requested offset) must be resolved from the filter tables, making that last segment more expensive. - -As described above, when the ACHS validity point moves or is past the requested offset, an info-level log message is emitted indicating that the stream fell back to the filter tables. Two corresponding metrics, `achs_skips` and `achs_midstream_fallbacks`, are available under `daml.participant.api.index` to help operators monitor the frequency of these fallbacks and tune the `valid-at-distance-target` accordingly. - -The `last-populated-distance-target` controls the additional lag (in event sequential IDs) for the population of ACHS in order to store only the long-lived contracts. A larger value reduces database I/O by skipping short-lived contracts that are created and archived before they would be added to the snapshot. However, setting it too large increases the cost of the remaining ACS tail, as more data must be fetched from the filter tables to cover the gap between the last populated point and the ACHS validity point. - -Further tuning parameters include: - -- `population-parallelism`: number of parallel threads for adding activations to the ACHS during normal operation. -- `removal-parallelism`: number of parallel threads for removing deactivated activations from the ACHS during normal operation. -- `aggregation-threshold`: minimum batch size (in event sequential IDs) before ACHS maintenance work is emitted. -- `init-parallelism`: number of parallel threads for ACHS population and removal during initialization. -- `init-aggregation-threshold`: minimum batch size (in event sequential IDs) for ACHS maintenance during initialization. -- `buffer-size`: size of the internal buffer between the indexer pipeline and the ACHS maintenance flow. - -The `deactivation_distances` histogram metric which is available under `daml.participant.api.indexer.deactivation_distances` can help operators understand the distribution of contract lifetimes (the event sequential ID distance between a contract's activation and its deactivation) and set an appropriate `last-populated-distance-target`. Ideally, the population distance should be large enough so that most short-lived contracts are already deactivated and thus not added to the snapshot. - -Three Prometheus gauge metrics are available under `daml.participant.api.indexer` to monitor the ACHS state: - -- `achs_valid_at`: the event sequential ID at which the ACHS is currently valid. ACS queries with a requested offset at or after this value can be read directly from the ACHS. -- `achs_last_populated`: the last event sequential ID for which activations were added to the ACHS. -- `achs_last_removed`: the last event sequential ID for which deactivations were looked up and the corresponding activations were removed from the ACHS. - -#### Other Ledger API Improvements - -- `ApiRequestLogger is now` also used by Ledger JSON API. Changes are: - - Redundant Request `TID` removed from logs. - - Additional CLI options added: `--log-access` captures API access logs in a separate file (default: `log/canton_access.log`), and `--log-access-errors` captures API access errors in a separate file (default: `log/canton_access_error.log`). - - Additional config options added: `debugInProcessRequests` logs in-process gRPC requests at DEBUG instead of TRACE, and `prefixGrpcAddresses` prefixes gRPC client addresses with `grpc:` (enabled by default). -- Ledger API `ListKnownParties` supports an optional prefix filter argument `filterParty`. The respective JSON API endpoint now additionally supports `identity-provider-id` as an optional argument, as well as `filter-party`. -- To protect the admin participant from self lock-out, it is now impossible for an admin to remove its own admin rights or delete itself. -- On Ledger API interface subscriptions, the `CreatedEvent.interface_views` now returns the ID of the package containing the interface implementation that was used to compute the specific interface view as `InterfaceView.implementation_package_id`. -- `OffsetCheckpoints` are now always generated when an open-ended update or completion stream is requested, even if there are no updates. The checkpoint can have the same offset as the exclusive start of the stream, making checkpoints visible even when starting from the ledger end. This enables client systems to recognize when the ledger end is advancing, even if the stream of updates is inactive. -- Extended the set of characters allowed in user-id in the ledger api to contain brackets: `()`. This also makes those characters accepted as part of the `sub` claims in JWT tokens. -- Functionality for managing internal and external parties has been improved, removing previous asymmetry: - - User rights can now be assigned to an external party during allocation. - - External parties can be allocated by the user themselves in the self-administration mode. Please note that users in self-administration mode can allocate up to N parties, depending on a setting of the parameter. - -``` -canton.participants..ledger-api.party-management-service.max-self-allocated-parties -``` - - By default the value of this parameter is 0\. - - -- An IDP administrator can now only allocate parties confined to their own IDP perimeter. - -### Important Changes - -#### Only Package-Name is Now Accepted for the Ledger API Queries - -The package-id reference format has been [deprecated since Canton 3.3](https://blog.digitalasset.com/developers/release-notes/splice-0.4.0-canton-3.3#a_identifier_addressing_by_package_name) and is no longer supported in this release for read Ledger API queries. This applies for both Protocol Version 34 and 35\. - -Specifying interface and template identifiers to the Ledger API read queries must use the package-name reference format, where the package name is the root identifier, such as `#::`. The package-id reference format was deprecated and is no longer supported so it will now fail. - -The impacted LAPIs are: - -- `GetUpdates` -- `GetUpdateByOffset` -- `GetUpdateById` -- `GetActiveContracts` -- `GetEventsByContractIdRequest` -- `SubmitAndWaitForTransaction` (the optional `transaction_format`) -- `SubmitAndWaitForReassignmentRequest` -- `ExecuteSubmissionAndWaitForTransactionRequest` - -#### `synchronizer_id` Format Changes in Protocol Version 35 - -In PV 35, the `synchronizer_id` field in an externally signed prepared transaction metadata will be populated with the physical synchronizer ID of the synchronizer on which the transaction will be processed, instead of the logical synchronizer ID, as is the case in PV 34\. Applications must ensure they do not rely on the format of the synchronizer\_id value. This was announced in this [Canton Forum post](https://forum.canton.network/t/format-of-synchronizer-id-will-change-in-canton-3-5-potential-breaking-change/8445). - -The change is that the format of the [`synchronizer_id`](https://github.com/digital-asset/canton/blob/8d252e6592354ac244eb65c850c4ef2a9a9b63c2/community/ledger-api-proto/src/main/protobuf/com/daml/ledger/api/v2/interactive/interactive_submission_service.proto#L187) metadata field value in a prepared transaction will change when upgrading from Canton 3.4 (protocol version 34\) to Canton 3.5 (protocol version 35). This is not an API breaking change but a change to the format of the `synchronizer_id` field shown below: - -**Current format example (protocol version 34):** -**`global_sync::12204457ac942c4d839331d402f82ecc941c6232de06a88097ade653350a2d6fc9c5`** - -**New format example (protocol version 35):** -**`global_sync::12204457ac942c4d839331d402f82ecc941c6232de06a88097ade653350a2d6fc9c5::35-0`** - -As shown, the 3.5 format adds a suffix (`::35-0`) compared to the current 3.4 format. Applications must ensure that they do not rely on the format of this field in a way that would break functionality. If the field must be parsed then it is recommended to support both formats. - -NOTE: This will cause the DSO Global Synchronizer ID to change as shown in the example above. - -Please note that the API specification does not give any guarantee on the `synchronizer_id` format, so the recommended approach is to treat `synchronizer_id` metadata as an opaque string in the application logic. In general, this is the recommended approach since the `synchronizer_id` field may change in the future. If you parse the `synchronizer_id`, the recommendation is to support both formats. - -#### Ledger API Specification Changes - -The OpenAPI and AsyncAPI specifications for the Ledger API (LAPI) are now aligned with the gRPC transport. All gRPC `optional` fields are now marked as `optional` in the other specifications, which can impact the generated code. This is mostly transparent for several OpenAPI (AsyncAPI) language generators because it is a backwards compatible step to go from `required` to `optional`. - -A summary of the changes to the generated code varies by language: - -* No changes are expected for Java. -* TypeScript will require minor changes. -* Clients in languages like Rust will need more (but trivial) changes. -* Languages using dynamic typing should not be affected. - -Refer to the *Appendix: OpenAPI / AsyncAPI Migration impact for users* for the migration guidance which is at the end of this release note. - -To support multiple OpenAPI specifications in a single Canton release while allowing for backwards compatibility, the OpenAPI (AsyncAPI) file name is now aligned with the corresponding canton version. For example: `openapi-3.5.0.yaml.` - -For backward compatibility, the Canton 3.4 OpenAPI and AsyncAPI specifications can be used unchanged. If you want to use new endpoints, features or leverage the new less strict spec, migrate to the new 3.5 OpenAPI/AsyncAPI specifications. - -#### Maximum Number of Signatures per External Submission - -As an availability security measure, the Ledger API now enforces a maximum number of signatures per party that can be provided for external submissions. This value defaults to 50 and can be changed at the following config path: `canton.participants..ledger-api.interactive-submission-service.maximum-number-of-signatures-per-party` - -#### Removed Deprecated `UpdateService` JSON APIs - -Several `UpdateService` JSON APIs were [deprecated in Canton 3.4](https://blog.digitalasset.com/developers/release-notes/canton-3.4-release-notes-for-splice-0.5.0#changes-json-api). These `UpdateService` requests are removed in this release: - -* `/v2/updates/trees` -* `/v2/updates/transaction-tree-by-offset` -* `/v2/updates/transaction-tree-by-id` -* `/v2/updates/transaction-by-offset` -* `/v2/updates/transaction-by-id` - -### New Deprecations - -#### Ledger JSON API Package Vetting Endpoints - -The Ledger JSON API `v2/package-vetting` endpoint exposes list functionality on the `GET` method by accepting a request body. This is not recommended by the HTTP specification, hence the endpoint is deprecated. For consistency, the `POST` method, used for updating the vetting state, of the same endpoint is also deprecated. - -In turn, two new endpoints are implemented to provide the same functionality: - -* `v2/package-vetting/list` accepts a `POST` request with the same body as the deprecated `GET v2/package-vetting` endpoint and returns the list of vetted packages in the same format. -* `v2/package-vetting/update` accepts a `POST` request with the same body as the deprecated `POST endpoint v2/package-vetting` and returns the updated vetting state of the package in the same format. - -#### Scope Based JWT Tokens - -Scope based JWT tokens that are identified by the `scope` claim in their body are deprecated in this release and will be removed in version 3.7. Going forward only the audience based tokens identified by their `aud` claim will be supported. In keeping with this change, the configuration entry allowing specifying the target scope expected on incoming JWT tokens has been deprecated as well. - -If you are currently using the scope based tokens, - -* Reconfigure your IDP system to issue `aud` based tokens instead and -* Change the Canton configuration accordingly specifying the expected target audience. - -Similar reconfiguration must be performed for each IDP configured through the Identity Provider Config Service. - -## Admin API and Console - -### New Features and Enhancements - -#### Improved Party and Repair ACS Imports - -For performance reasons, the ACS import endpoints for both party replication and participant repair were overhauled to be memory-efficient streaming endpoints: - -* Console command `participant.parties.import_party_acs` -* Console command `participant.repair.import_acs` -* gRPC RPC `PartyManagementService.ImportPartyAcs` -* gRPC RPC `ParticipantRepairService.ImportAcs` - -This resolves previous memory limitations, as these endpoints no longer load the entire ACS snapshot into memory at once. This works for both Protocol Version 34 and 35\. - -#### Changes to use the New Endpoints - -**The `synchronizerId` is now a mandatory first parameter for both the `import_party_acs` and `import_acs console` commands as well as their analogous gRPC endpoints.** You will need to update any existing scripts. - -For `import_party_acs`: - -* Old usage: `participant.parties.import_party_acs("canton-ACS-export.gz")` -* New usage: `participant.parties.import_party_acs(mySynchronizerId, importFilePath = "canton-ACS-export.gz")` - - -For import\_acs: - -* Old usage: `participant.repair.import_acs("canton-ACS-export.gz")` -* New usage: `participant.repair.import_acs(mySynchronizerId, importFilePath = "canton-ACS-export.gz")` - -Because of the mandatory `synchronizerId` parameter, to import a multi-synchronizer ACS snapshot, you must now call the endpoint sequentially for each synchronizer your participant is connected to, using the exact same snapshot file. The import process will ignore any contracts in the snapshot that are associated with a different synchronizer. - -#### Details on the grpc `ImportAcs` Repair Endpoint - -The `ImportAcs` and `ImportAcsV2` RPCs have been consolidated, introducing the following changes and migration steps: - -* `ImportAcsV2` (along with its request/response messages) is completely removed. All clients must migrate to the standard `ImportAcs` RPC. -* Request signature and type changes: - * Fields `workflow_id_prefix` (2), `contract_import_mode` (3), and `representative_package_id_override` (5) in `ImportAcsRequest` are now explicitly optional. - * A new optional string `synchronizer_id` \= 6 field was added. - * Migration (ScalaPB): Adding optional changes generated code from base types to `Option[T]`. Existing clients will fail to compile and must be updated to wrap assigned values (e.g., `workflowIdPrefix = Some("prefix")`) and explicitly handle reading `Option` types. -* Behavioral change (`synchronizer_id`): When filtering by synchronizer, mismatched contracts are now ignored. This breaks previous logic that relied on the import strictly aborting upon a mismatch. - -#### Details on the gRPC ImportPartyAcs Party Replication Endpoint - -The `ImportPartyAcs` endpoint underwent the exact same consolidation (removing `ImportPartyAcsV2`), streaming semantics updates, generated code changes (`ScalaPB Option[T]`), and mismatched synchronizer behavior (ignoring rather than failing) as `ImportAcs`. - -Key differences specific to `ImportPartyAcs`: - -* A new optional `string party_id = 6` field was added. Providing this in the first request of the stream enables automatic, crash-resilient scheduling of the onboarding flag clearance. If omitted, the participant logs a warning, and the flag must be cleared manually. -* The `synchronizer_id` (field 2\) temporarily accepts either a logical or physical synchronizer ID to better support Logical Synchronizer Upgrade (LSU) scenarios. This support is subject to change. - -#### Removal of Legacy ACS Export and Import Endpoints - -The following legacy repair endpoints for the ACS export and import were [deprecated in Canton 3.4](https://blog.digitalasset.com/developers/release-notes/canton-3.4-release-notes-for-splice-0.5.0#changes-acs-import) and are removed in this release: - -* Console `command participant.repair.export_acs_old` -* Console `command participant.repair.import_acs_old` -* gRPC `rpc ParticipantRepairService.ExportAcsOld` -* gRPC `rpc ParticipantRepairService.ImportAcsOld` - -To use the repair endpoints simply remove the 'old' suffix: - -* Migrate to `participant.repair.export_acs` from `participant.repair.export_acs_old` -* Migrate to `participant.repair.import_acs` from `participant.repair.import_acs_old` -* Migrate to `ParticipantRepairService.ExportAcs` from `ParticipantRepairService.ExportAcsOld` -* Migrate to `ParticipantRepairService.ImportAcs` from `ParticipantRepairService.ImportAcsOld` - -Note that previously created ACS snapshots with the legacy endpoints cannot be imported with the current endpoints as the underlying data format has completely changed. - -**Migrating to `export_acs`** -The most significant change is the removal of the timestamp parameter, which has been replaced by a mandatory `ledgerOffset` parameter. - -Console parameter changes: - -* New mandatory parameter: `ledgerOffset (Long)`. You must now specify the exact ledger offset for the snapshot instead of a timestamp. -* Removed parameters: `partiesOffboarding`, `timestamp` (replaced by `ledgerOffset`), force. -* Renamed parameters: `outputFile` is now `exportFilePath` (default is `"canton-acs-export.gz"`), `filterSynchronizerId` is now `synchronizerId`. -* New optional parameters: `excludedStakeholders` allows you to omit contracts that have one or more of these parties as a stakeholder; `contractSynchronizerRenames` allows mapping contracts from one synchronizer to another during export. - -gRPC changes for `ExportAcsRequest`: - -* `parties` to `party_ids`: Field renamed for consistency. If left empty, the endpoint will act as a wildcard and export the ACS for all parties hosted by the participant. -* `timestamp` to `ledger_offset` (Breaking): You must provide an exact `int64 ledger_offset` instead of a timestamp. -* `Filter_synchronizer_id` to `synchronizer_id`: Field renamed for consistency. -* Removed fields: `force` and `parties_offboarding` have been completely removed. -* New fields: `contract_synchronizer_renames` and `excluded_stakeholder_ids`. - -**Migrating to `import_acs`** -The import command remains largely the same in basic usage, but introduces new optional parameters for advanced validation and overrides, alongside strict memory-efficient streaming semantics for gRPC. - -Console parameter changes: - -* Renamed parameter: `inputFile` is now `importFilePath` (default is `"canton-acs-export.gz"`). -* New optional parameters: `contractImportMode` governs contract validation upon import (defaults to `ContractImportMode.Validation`); `representativePackageIdOverride` allows overriding representative package IDs during import; `excludedStakeholders` allows omitting contracts that have one or more of these parties as a stakeholder. - -gRPC changes for `ImportAcsRequest`: - -* Streaming Semantics (Breaking): The new endpoint requires metadata fields (like `contract_import_mode`, `synchronizer_id`, etc.) to be populated only in the first request of the stream. Subsequent requests must omit metadata and only contain the binary `acs_snapshot` chunks. -* New mandatory fields: `contract_import_mode` and `synchronizer_id` must be explicitly defined in the first stream request. -* Removed fields: `allow_contract_id_suffix_recomputation` is completely removed. -* New fields: `excluded_stakeholder_ids` and `representative_package_id_override`. -* Response update: `ImportAcsResponse` is now a completely empty message (previously returned a contract ID mapping). - -#### Improvements for `repair.add` - -The `participant.repair.add` admin command has been revised to use the new `ImportAcs` backend, bringing significant memory performance improvements, stricter default safety validations, and several new parameters. - -Previously, `repair.add` implicitly accepted all injected contracts without re-evaluating their cryptographic hashes. To prevent accidental data corruption, the command now defaults to `Validation` mode (`contractImportMode = ContractImportMode.Validation`). - -So, if you have existing scripts or recovery procedures that inject manually modified, synthetic, or inconsistent contracts (where the payload does not strictly match the ContractId hash), they will now fail with a `"Failed to authenticate contract with id"` error. - -To restore the legacy behavior and bypass this cryptographic validation, explicitly pass the `Accept` mode in your command call: - -``` -participant.repair.add( - synchronizerId = mySynchronizer, - protocolVersion = myProtocolVersion, - contracts = myContracts, - contractImportMode = ContractImportMode.Accept // Bypasses strict validation - ) -``` - -The command signature has been expanded to support several optional parameters: - -* `workflowIdPrefix`: Allows you to set a custom prefix for the generated workflow ID to easily track the repair transactions (defaults to import-\). -* `contractImportMode`: Choose between Validation (default, validates that contract IDs comply with the scheme associated with the synchronizer where the contracts are assigned), or `Accept` the contracts as they are (if you know what you are doing). -* `representativePackageIdOverride`: Allows you to remap or override the representative package IDs of the contracts as they are imported. -* `excludedStakeholders`: When defined, any contract that has one or more of these parties as a stakeholder will not be added. - -#### Admin API Error Reporting now Uses Canonical gRPC Error Propagation - -The previous method of returning errors via response fields has been replaced in favor of the consistency of canonical gRPC error propagation. The following fields are now obsolete: - -- `HandshakeResponse.value.failure` -- `VerifyActiveResponse.value.failure` - -Errors are now communicated strictly through `io.grpc.Status` codes to ensure a consistent and secure interface. - -The following status codes have changed as follows: - -- `SequencerAuthenticationService`.challenge now fails with `INVALID_ARGUMENT` (instead of `FAILED_PRECONDITION`), if the client does not support the sequencer's protocol version. -- `SequencerConnectService` now fails with `INVALID_ARGUMENT` (instead of `FAILED_PRECONDITION`) if a non-participant tries to connect. -- `SequencerConnectService.registerOnboardingTopologyTransactions` newly fails with `INTERNAL` (instead of `FAILED_PRECONDITIONS`) if there are missing dynamic synchronizer parameters. -- `SequencerConnectService.registerOnboardingTopologyTransactions` newly fails with `FAILED_PRECONDITION` if the transactions cannot be added to the topology state and sanitization of error messages is enabled. - -#### Single Topology Transaction for External Parties - -Multiple topology transactions for external parties can now be represented with a single `PartyToParticipant` topology transaction. - -The `generateExternalPartyTopology` endpoint on the Ledger API now returns a single `PartyToParticipant` topology transaction to onboard the party. The transaction contains a signing threshold and signing keys. This effectively deprecates the usage of `PartyToKeyMapping`. For parties with signing keys both in `PartyToParticipant` and `PartyToKeyMapping`, the keys from `PartyToParticipant` take precedence. - -Deprecated usage of `PartyToKeyMapping`. The functionality provided by `PartyToKeyMapping` is now available directly in `PartyToParticipant`. Please use `PartyToParticipant` for new transactions. `PartyToKeyMapping` is still fully supported in this version (including existing and new transactions). In a future version, creation of new `PartyToKeyMapping` transactions may be disallowed. - -Deprecated `TopologyManagerReadService.ExportTopologySnapshot` and `TopologyManagerWriteService.ImportTopologySnapshot`, along with their console counterparts `topology.transactions.export_topology_snapshot`, `topology.transactions.import_topology_snapshot`, `topology.transactions.import_topology_snapshot_from`, and `topology.transactions.export_identity_transactions`. Please use the corresponding V2 variants (`ExportTopologySnapshotV2` / `ImportTopologySnapshotV2`, `export_topology_snapshotV2`, `import_topology_snapshotV2`, `import_topology_snapshot_fromV2`, `export_identity_transactionsV2`) instead, which use an updated internal bytestring format. - -#### Offline Party Replication - -Concluding an offline party replication by clearing the onboarding flag now includes two major updates when using protocol version 35: - -- Additional crash resilience for ongoing clearances. -- Automatic scheduling for clearances when a participant (re)connects to the synchronizer. - -These changes apply only to the `participant.parties.import_party_acs` and `participant.parties.clear_party_onboarding_flag` endpoints. - -Note: The replicated party ID must be included in the party ACS import call to enable automatic scheduling. The original behaviour is retained for protocol version 34\. - -#### Hardened Error Handling in Sequencer Connect Service - -We more completely redacted sensitive information from error messages for the `SequencerConnectService` to avoid information leakage. Detailed internal error messages are now redacted before being sent to clients. - -If detailed diagnostics are required in a non-production environment, sanitization can be toggled off via: - -``` -canton.monitoring.sanitize-public-error-messages = false -``` - -#### Mediator Verdicts Resilience - -The mediator now guarantees that all verdicts will eventually be persisted and available on the inspection API. - -#### Enhanced Reliability for `GetHighestOffsetByTimestamp` - -Previously, the `GetHighestOffsetByTimestamp` RPC and the `find_highest_offset_by_timestamp` console command could return offsets not yet synced with the participant's local cache due to a race condition. In this circumstance, using this returned value with a future timestamp resulted in an error. - -Specific changes: - -- The required state is now retrieved atomically via a consistent database snapshot. -- The endpoint now includes an internal barrier (waiting up to 10 seconds) to ensure the local Ledger API cache catches up with the database before returning the offset. -- When `force` is true, requesting a future timestamp now gracefully returns the current ledger end instead of failing. - -No migration required. - -#### Topology-Aware Package Selection (TAPS) improvements - -Topology-Aware Package Selection (TAPS) better handles inconsistent vetting states: - -- The algorithm now considers a party's package vetting state only for packages required by that party in the interpreted transaction. It starts with a minimal set of restrictions derived from the command's root nodes and progressively accumulates more restrictions over a configurable number of passes. This iterative process increases the likelihood of finding a valid package selection set for the routing of the transaction. -- The maximum number of TAPS passes can be set at the request-level via the optional `taps_max_passes` field in `Commands` or `PrepareSubmissionRequest` messages. If not specified, the default value is taken from the participant configuration via `participants.participant.ledger-api.topology-aware-package-selection.max-passes-default` (defaults to `3`). A hard limit is enforced by `participants.participant.ledger-api.topology-aware-package-selection.max-passes-limit` (defaults to `4`). -- TAPS now ignores unvetted dependencies of packages that are not required for interpretation. complying now with the support of unvetted dependencies in the Canton protocol. - -#### Online Party Replication - -Online party replication available as an Alpha feature. High level changes and additions are: - -* Added the file-based online party replication command `participant.parties.add_party_with_acs_async` to be used along with `participant.parties.export_party_acs` and instead of the sequencer-channel-based `add_party_async` command. -* The online party replication status command now returns status in a very different, "vector-status" format rather than the old "oneof" style. This impacts the `participant.parties.get_add_party_status` command and `com.digitalasset.canton.admin.participant.v30.PartyManagementService.GetAddPartyStatus` gRPC response type. -* The participant configuration to enable online party replication has been renamed to `alpha-online-party-replication-support` from `unsafe-online-party-replication` for consistency with other alpha features and to reflect that the default file-based mode is more secure not relying on sequencer channels. -* The sequencer configuration to enable sequencer channels for online party replication has been renamed to `unsafe-sequencer-channel-support` from `unsafe-enable-online-party-replication` for consistency and to refer specifically to sequencer channels. - -Please consult the documentation for further details. - -#### ACS Ledger API Counting - -The new memory-efficient console command `participant.ledger_api.acs.count()` has been introduced to count the number of active contracts on a participant node. - -Note: This command is currently under the Testing feature flag. - -### Important Changes - -#### Removal of Automatic Recomputation of Contract IDs upon ACS Import - -The ability to recompute contract IDs upon ACS import has been removed. This is a result of the many improvements for ACS import and export. - -#### Changes from `NonNegativeLong` to `Long` - -Some console commands using a `NonNegativeLong` for the offset are changed to accept a `Long` instead. Similarly, some console commands returning an offset now return a `Long` instead of a `NonNegativeLong`. It brings consistency and allows passing the output of `participant.ledger_api.state.end()`. - -Impacted commands: - -* `participant.repair.export_acs` -* `participant.parties.find_party_max_activation_offset` -* `participant.parties.find_party_max_deactivation_offset` -* `participant.parties.find_highest_offset_by_timestamp` - -#### Removal of Legacy Party Replication Repair Console Macros - -The original party replication method, which relied on a silent synchronizer, has been superseded by the offline party replication process. Consequently, the obsolete repair console macros associated with the legacy approach are no longer needed and have been removed. - -Specifically, the following macros are no longer available: - -- `step1_hold_and_store_acs` -- `step2_import_acs` - -If you previously relied on the *Silent synchronizer replication procedure*, you will need to transition to the current offline party replication process. For details, please consult the [Offline Party Replication documentation](https://docs.canton.network/global-synchronizer/production-operations/party-management#offline-party-replication) - -#### Miscellaneous Console Changes - -- Removed the `LastErrorsAppender` along with the Admin API endpoints `StatusService.GetLastErrors` and `StatusServiceGetLastErrorTrace`, as well as the corresponding console commands `last_errors` and `last_error_trace`. - -### New Deprecations - -#### Protocol Version parameter in Topology List Commands - -The `protocolVersion` parameter in all `.topology..list` console commands has been deprecated and will be removed in a future version. - -## Deployment and Configuration - -### New Features and Enhancements - -#### Session Signing Keys - -Session signing keys can now be used to reduce the number of calls to external KMS (Key Management Service) providers. When enabled, session signing keys are generated and cached locally for a limited duration and used for signing operations during their validity period. - -Please read the documentation on [Session Signing Keys](https://docs.canton.network/overview/learn/cryptographic-keys#session-encryption-keys) for details on how to enable and configure this feature. Session signing keys are only available from Protocol Version 35 and are not enabled by default. - -#### Multi-Synchronizer Improvements - -Multi-synchronizer support has been part of the Canton 3.x distribution from its initial release. The recommendation is to start developing multi-synchronizer applications using this release. The main components for multi-synchronizer development, including the surface APIs for [multiple synchronizer connection configurations](https://docs.canton.network/global-synchronizer/reference/canton-console-commands#synchronizer-connectivity), are implemented and the [mechanics are documented](https://docs.canton.network/overview/learn/multi-synchronizer#transactions-with-multiple-synchronizers). As an example, Digital Asset maintains a multi-synchronizer sample application “[Splitwell](https://github.com/hyperledger-labs/splice/tree/main/apps/splitwell)” and continuously tests that it works in a multi-synchronizer deployment as part of [CI/CD testing](https://github.com/hyperledger-labs/splice/blob/main/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellIntegrationTest.scala). - -Integration testing can be done by deploying your own ScratchNet (to mimic the Global Synchronizer and another private synchronizer), along with setting two configuration values. The configuration values to be set to fully enable multi-synchronizer are: - -* `EnableMultiSynchronizer` enables the validator to perform assign and unassign operations (referred to as reassignment). -* `enable-all-ledger-api-reassignments` is a multi-synchronizer setting so that if an ACS import or the repair service is done, it will signal the ACS import using assigned events (instead of created). This is necessary if the ACS snapshot contains contracts with non-zero reassignment counters. - -To enable contract reassignment across synchronizers, the flag `EnableMultiSynchronizer` must be activated on all validators hosting a stakeholder of the contract on both the source and target synchronizers. For a validator, `EnableMultiSynchronizer` is enabled as follows: - -``` -participant.topology.synchronizer_trust_certificates.propose( - p.id, - synchronizerId, - featureFlags = Seq(ParticipantTopologyFeatureFlag.EnableMultiSynchronizer), -) -``` - -The flag `enable-all-ledger-api-reassignments` is a new `Boolean` participant node parameter which differentiates between the types of events when an ACS import is performed: - -* The default is `false` so ACS import or repair service will generate standard `Create` events. -* Setting it to `true` enables `Assign` events instead. - -This flag preserves the reassignment counter value of a contract on an ACS import. Using the default (`Create` events) resets this counter to zero upon an ACS import. Ledger API clients will need to accommodate the Assign/Unassign events when this is enabled. - -These flags also need to be set if integration testing with DevNet is performed. - -#### Other Deployment and Configuration Enhancements - -- Added a field `MaxConcurrentCallsPerConnection` and corresponding default `defaultMaxConcurrentCallsPerConnection` (set to 100000\) to `ServerConfig`. This corresponds to `max-concurrent-streams-per-connection` in the app configs, e.g., `docker/canton/images/canton-sequencer/app.conf` and can be changed there. At present the value for sequencers is configured to be 500 for the public API and 100 for the Admin API. -- Added network timeout and `client_connection_check_interval` for db operations in the Ledger API server and indexer to avoid hanging connections for Postgres (see `PostgresDataSourceConfig`). The defaults are 60 seconds network timeout and 5 seconds `client_connection_check_interval` for the Ledger API server, and 20 seconds network timeout and 5 seconds `client_connection_check_interval` for the indexer. These values can be configured via the new configuration parameters `canton.participants..ledger-api.postgres-data-source.network-timeout` for network timeout of the Ledger API server and `canton.participants..parameters.ledger-api-server.indexer.postgres-data-source.client-connection-check-interval` for the client\_connection\_check\_interval of the indexer. -- `.replication.connection-pool.connection.client-connection-check-interval` is introduced that allows configuring the PostgreSQL-specific `client_connection_check_interval` parameter for DB locked connections. This is a safety mechanism to prevent hanging connections in case of network issues. The default value is 5 seconds. -- This value defaults to 50 and can be changed at the following config path: `canton.participants..ledger-api.interactive-submission-service.maximum-number-of-signatures-per-party` -- Added a new configuration parameter `canton.participants..ledger-api.index-service.max-lookup-limit` that caps the maximum number of contracts returned by a contract key lookup per request. The default value is 1000\. -- When the `AcsCommitmentProcessor` is initializing, read stakeholder groups from the snapshot in batches of size `canton.parameters.general.batching.max-stakeholder-groups-batch-size` (default 1000), rather than all at once. This allows early termination of this initialization if the node is shutting down. -- The release version is now exposed in `NodeStatus.NotInitialized`, so the node version can be retrieved even before the node is initialized. - -### Important Changes - -#### PQS and Daml Shell Docker Image Changes - -In past releases a single docker image supported multiple PQS major and minor versions, with additional environment variable setup to select the PQS version to run. This has been simplified where each PQS `major.minor` version has their own separate Docker image. Please see the [PQS download documentation](https://docs.canton.network/sdks-tools/development-tools/pqs#install-and-download) for details. - -The same simplification has been made to the Daml Shell Docker image and is described [here](https://docs.canton.network/sdks-tools/cli-tools/daml-shell#download). - -#### Removal of Multi-Host Name Resolution Tooling - -Support for the multi-host name resolution was removed. This was only used if synchronizer connectivity defined a sequencer with multiple endpoints, which is not supported with our current sequencers: we now have multiple sequencers each with exactly one endpoint. - -#### Removal of the Old Sequencer Connection Transports - -The old sequencer connections transports have been removed, and only the new sequencer connection pool remains. Consequently, the configuration `.sequencer-client.use-new-connection-pool` has been deprecated and no longer has any effect. - -#### Other Important Changes - -- The expert `keep-alive-client` configuration parameter for various client services moved to `channel.keep-alive-client`. -- We reduced the defaults for `setBalanceRequestSubmissionWindowSize` and `defaultMaxSequencingTimeOffset` to 2 minutes. -- The default OTLP gRPC port that the Canton connects to in order to export the traces has been changed from 4318 to 4317\. This aligns the default configuration of Canton with the default configuration of the OpenTelemetry Collector. This change affects only the users who have configured an OTLP trace export through `canton.monitoring.tracing.tracer.exporter.type=otlp` - -### New Deprecations - -#### Deprecate Initial Protocol Version Configuration - -The config key `participant.parameters.initial-protocol-version` was unused and has been marked as deprecated. - -#### Canton Configuration Deprecations - -* The configuration parameters `topology.use-new-processor` and `topology.use-new-client` have been deprecated and now default to `true`. Configuring those parameters to false will be ignored. -* The parameter `canton.participants..parameters.package-metadata-view.init-takes-too-long-interval` is now ignored, and a warning will only be printed once, rather than periodically. -* The parameter `canton.participants..parameters.ledger-api-server.indexer.prepare-package-metadata-time-out-warning` is now ignored. -* The individual JVM metric flags classes, cpu, memoryPools, threads, gc, and buffers in `canton.monitoring.metrics.jvm-metrics` are no longer supported since the upgrade to OpenTelemetry instrumentation 2.26.0. All standard JVM metrics (classes, cpu, memory pools, threads, garbage collector) are now always enabled when `jvm-metrics.enabled = true`. A new experimental flag has been added to control experimental JVM metrics (e.g. buffer pools). Users who previously set `buffers = true` should migrate to `experimental = true`. See [open-telemetry/opentelemetry-java-instrumentation\#16087](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/16087) for details. -* The Zipkin trace exporter configuration `canton.monitoring.tracing.tracer.exporter.type=zipkin` is deprecated following the OpenTelemetry specification deprecation of Zipkin exporters. The Zipkin exporter will be removed in a future release. Users should migrate to the OTLP exporter. See [https://opentelemetry.io/blog/2025/deprecating-zipkin-exporters/](https://opentelemetry.io/blog/2025/deprecating-zipkin-exporters/) for details. -* Removed the feature flag `canton.sequencers..parameters.async-writer.enabled`, as async writing is now the only supported mode. -* Changed the path for `crypto.kms.session-signing-keys` (deprecated) to `crypto.session-signing-keys` so that session signing key configuration is no longer directly tied to a KMS. However, session signing keys can still only be enabled when using a KMS provider or when running with `non-standard-config=true`. -* `package-dependency-cache` field in caching configuration is deprecated. It can be removed safely from node configurations. - -## Operational Procedures - -### New Features and Enhancements - -#### Change from `grpcurl` to `grpc-health-probe` in all Docker Images - -The tool used for health check probes changed from `grpcurl` to `grpc-health-probe` in all the docker images. - -### Important Changes - -#### `Offline Root Namespace Key` Script Updates - -The helper offline root namespace key scripts have the following changes: - -* Renamed `prepare-certs.sh` to `prepare-cert.sh` -* Changed `assemble-certs.sh` to automatically suffix the generated certificate with a `.cert` extension, similarly to what is being done in `prepare-cert.sh` -* Removed the `10-offline-root-namespace-init` example folder as its content is now integrated in the [documented how-to](https://docs.canton.network/global-synchronizer/production-operations/key-management#offline-root-namespace-key). -* Committed the buf image necessary to run the script to the repository (also available in the release artifact), making usage from the open source repo easier - -### New Deprecations - -None - -## Compatibility - -The following Canton protocol versions are supported: - -| Dependency | Version | -|----------------------------|----------------------------| -| Canton protocol versions | 34, 35 | - -Canton has been tested against the following versions of its dependencies: - -| Dependency | Version | -|----------------------------|----------------------------| -| Java Runtime | OpenJDK 64-Bit Server VM (build 21.0.10+7-nixos, mixed mode, sharing) | -| Postgres | Recommended: PostgreSQL 17.9 (Debian 17.9-1.pgdg13+1) – Also tested: PostgreSQL 14.23 (Debian 14.23-1.pgdg13+1), PostgreSQL 15.18 (Debian 15.18-1.pgdg13+1), PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) | - - -## Appendix: OpenAPI / AsyncAPI Migration - -### Runtime Compatibility - -JSON Messages are compatible so a client built using existing (for instance 3.4.9) code will continue to work properly. - -### Compile-Time Compatibility of the Generated Code - -Users that replace the 3.4 `openapi.yaml` with a new one and regenerate code might be forced to fix compilation errors because some parameters have changed from required to optional(or vice versa). - -However, users may decide to continue to use previous versions of OpenAPI. -In that case there are no changes required to code. - -Information about compatible OpenAPI versions for each release will be in the RELEASE NOTES and openapi.yaml files. - -If you decide to use a newer openapi file and regenerate the binding code, the impact of this change varies by language: - -* No changes are expected for Java. -* TypeScript will require minor changes (use of \!\! and ?. operators). -* Clients in languages like Rust will need more (but trivial) changes (use of Option and unwrapping). -* Languages using dynamic typing should not be affected. - -Migration instructions are provided below. The example code below is generated from canton Ledger JSON Api openapi.yaml using [https://openapi-generator.tech/docs/installation/](https://openapi-generator.tech/docs/installation/) with default options for each language. The code examples are fragments of `CompletionStreamRequest and JsGetActiveContractsResponse` with a before and after comparison. - -#### TypeScript Example - -Before: - -``` -export class CompletionStreamRequest { - /** … - Required unless authentication is used with a user token. - */ - 'userId': string; - /** … - * Required - */ - 'parties'?: Array; - /** - * This optional field… - */ - 'beginExclusive': number; -``` - -After: - -``` -export class CompletionStreamRequest { - /** - * … Required unless authentication is used with a user token… - */ - 'userId'?: string; - /** - * … Required - */ - 'parties'?: Array; - /** - * …. Optional - */ - 'beginExclusive'?: number; -``` - -This is a code compatible change, as it is possible to assign \`T\` to a variable of type \`T?\`. -Before: - -``` -export class JsGetActiveContractsResponse { - /** - *... Optional - */ - 'workflowId': string; - 'contractEntry': JsContractEntry; -``` - -After: - -``` -export class JsGetActiveContractsResponse { - /** - * …Optional - */ - 'workflowId'?: string; - 'contractEntry'?: JsContractEntry; -``` - -This might require code changes, in places previously clients were expecting values, now can be null/undefined values. It means use of operator ?? or some other if checking will be needed. -In practice \- while converting from gRPC proto those values will be always populated anyway. - -Migration instructions (proposal): - -If you decide to use new openapi yaml with a typescript projects, you might get compilation errors such as: - **error TS2322: Type 'string | undefined' is not assignable to type 'string'.** - -Use \! or ?? to fix the error. - -``` -//before -let party:string = externalPartyTopologyResponse.partyId; -//after -let party:string = externalPartyTopologyResponse.partyId!; -``` - -Summary: Generally Typescript developers are not expected to do any changes in code when creating requests. - -There are however trivial changes expected when processing results. - -We will put a note in NOTES and openapi description that users might continue to use old openapi.yaml. - -#### Java Example - -There are various code generators for java and popular ones such as [https://openapi-generator.tech/docs/generators/java/](https://openapi-generator.tech/docs/generators/java/) provides tons of options. Given the way Java treats optional fields, we expect them all to behave as described below, although we cannot guarantee it in all instances.. (Optional in Java should be used as return type only. There are libraries such as Guava or Vavr that provide an alternative Optional, but they are not popular). - -Before: - -``` -public class CompletionStreamRequest { - public static final String SERIALIZED_NAME_USER_ID = "userId"; - @SerializedName(SERIALIZED_NAME_USER_ID) - @javax.annotation.Nonnull - private String userId; - - public static final String SERIALIZED_NAME_PARTIES = "parties"; - @SerializedName(SERIALIZED_NAME_PARTIES) - @javax.annotation.Nullable - private List parties = new ArrayList<>(); - - public static final String SERIALIZED_NAME_BEGIN_EXCLUSIVE = "beginExclusive"; - @SerializedName(SERIALIZED_NAME_BEGIN_EXCLUSIVE) - @javax.annotation.Nonnull - private Long beginExclusive; -``` - -After: - -``` -|public class CompletionStreamRequest { - public static final String SERIALIZED_NAME_USER_ID = "userId"; - @SerializedName(SERIALIZED_NAME_USER_ID) - @javax.annotation.Nullable - private String userId; - - public static final String SERIALIZED_NAME_PARTIES = "parties"; - @SerializedName(SERIALIZED_NAME_PARTIES) - @javax.annotation.Nullable - private List parties = new ArrayList<>(); - - public static final String SERIALIZED_NAME_BEGIN_EXCLUSIVE = "beginExclusive"; - @SerializedName(SERIALIZED_NAME_BEGIN_EXCLUSIVE) - @javax.annotation.Nullable - private Long beginExclusive; -``` - -Migration instruction for Java: -There are no expected changes needed in code if you use java. - -We generally do not expect any code changes for java, due to nullability not being enforced by the compiler. There might be some changes needed for people using linters. - -#### Rust Example - -Before: - -``` -pub struct CompletionStreamRequest { - /// …Required unless authentication … - #[serde(rename = "userId")] - pub user_id: String, - /// … Required - #[serde(rename = "parties", skip_serializing_if = "Option::is_none")] - pub parties: Option>, - /// This optional… - #[serde(rename = "beginExclusive")] - pub begin_exclusive: i64, -} -``` -After: - -``` -pub struct CompletionStreamRequest { - /// … Required unless … - #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] - pub user_id: Option, - /// …Required - #[serde(rename = "parties", skip_serializing_if = "Option::is_none")] - pub parties: Option>, - /// …Optional - #[serde(rename = "beginExclusive", skip_serializing_if = "Option::is_none")] - pub begin_exclusive: Option, -} -``` +- [Canton 3.5.6](/global-synchronizer/release-notes/canton-releases/3-5-6) +- [Canton 3.5.5](/global-synchronizer/release-notes/canton-releases/3-5-5) +- [Canton 3.5.4](/global-synchronizer/release-notes/canton-releases/3-5-4) +- [Canton 3.5.3](/global-synchronizer/release-notes/canton-releases/3-5-3) +- [Canton 3.5.2](/global-synchronizer/release-notes/canton-releases/3-5-2) +- [Canton 3.5.1](/global-synchronizer/release-notes/canton-releases/3-5-1) diff --git a/package.json b/package.json index 3a7017527..37e99071c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "scripts": { "generate:all-reference-docs": "python3 scripts/generate_all_reference_docs.py", + "update:canton-release-notes": "python3 scripts/update_canton_release_notes.py", "update:generated-reference-sources": "python3 scripts/update_generated_reference_sources.py", "generate:version-compatibility-dashboard": "python3 scripts/generate_network_component_versions.py", "generate:json-api-reference": "python3 scripts/generate_json_api_reference.py", diff --git a/scripts/summarize_version_changes.py b/scripts/summarize_version_changes.py index 1feee327f..f0e9d2278 100644 --- a/scripts/summarize_version_changes.py +++ b/scripts/summarize_version_changes.py @@ -4,6 +4,7 @@ import argparse import json +import re import sys from collections.abc import Iterable, Mapping from pathlib import Path @@ -15,6 +16,9 @@ "devnet": "DevNet", } +RELEASE_HEADING_RE = re.compile(r"^# Release of Canton (?P\d+\.\d+\.\d+)\s*$", re.MULTILINE) +CANTON_RELEASE_LINK_RE = re.compile(r"/global-synchronizer/release-notes/canton/(?P\d+-\d+-\d+)") + COMPONENT_LABELS = { "splice": "Splice", "damlSdk": "Canton / Daml SDK", @@ -218,6 +222,25 @@ def artifact_source_config_changes(before_path: Path, after_path: Path, *, label return changes +def canton_release_note_changes(before_path: Path, after_path: Path, *, label: str) -> list[str]: + before = before_path.read_text(encoding="utf-8") + after = after_path.read_text(encoding="utf-8") + before_versions = set(RELEASE_HEADING_RE.findall(before)) + before_versions.update(match.group("slug").replace("-", ".") for match in CANTON_RELEASE_LINK_RE.finditer(before)) + after_versions = set(RELEASE_HEADING_RE.findall(after)) + after_versions.update(match.group("slug").replace("-", ".") for match in CANTON_RELEASE_LINK_RE.finditer(after)) + added = sorted(after_versions - before_versions) + removed = sorted(before_versions - after_versions) + changes = [] + if added: + changes.append(f"- {label}: added {', '.join(added)}") + if removed: + changes.append(f"- {label}: removed {', '.join(removed)}") + if not changes and before_versions != after_versions: + changes.append(f"- {label}: refreshed release index") + return changes + + def print_changes(changes: list[str]) -> None: if changes: print("\n".join(changes)) diff --git a/scripts/update_canton_release_notes.py b/scripts/update_canton_release_notes.py new file mode 100644 index 000000000..7eba720f7 --- /dev/null +++ b/scripts/update_canton_release_notes.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import base64 +import json +import os +import re +import sys +import urllib.error +import urllib.parse +import urllib.request +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterable, Sequence + + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_SOURCE_REPO = "digital-asset/canton" +DEFAULT_SOURCE_REF = "main" +DOCS_MAIN = REPO_ROOT / "docs-main" +DEFAULT_RELEASE_INDEX = DOCS_MAIN / "global-synchronizer" / "release-notes" / "canton.mdx" +DEFAULT_RELEASE_DIR = DOCS_MAIN / "global-synchronizer" / "release-notes" / "canton-releases" +DEFAULT_LEGACY_RELEASE_PAGE = DOCS_MAIN / "global-synchronizer" / "release-notes" / "canton" / "index.mdx" +DEFAULT_LEGACY_RELEASE_DIR = DOCS_MAIN / "global-synchronizer" / "release-notes" / "canton" +DEFAULT_DOCS_JSON = DOCS_MAIN / "docs.json" +GITHUB_API = "https://api.github.com" +USER_AGENT = "cf-docs-canton-release-note-updater" +RELEASE_HEADING_RE = re.compile(r"^# Release of Canton (?P\d+\.\d+\.\d+)\s*$", re.MULTILINE) +RELEASE_NOTE_FILE_RE = re.compile(r"^(?P\d+\.\d+\.\d+)\.md$") +CANTON_RELEASE_LINK_RE = re.compile( + r"/global-synchronizer/release-notes/(?:canton|canton-releases)/(?P\d+-\d+-\d+)" +) +ANGLE_PLACEHOLDER_RE = re.compile(r"<([^>\n]+)>") +CANTON_PAGE_REF = "global-synchronizer/release-notes/canton" +CANTON_RELEASE_PAGE_ROOT = "global-synchronizer/release-notes/canton-releases" +CANTON_PAGE_TITLE = "Canton" +CANTON_PAGE_DESCRIPTION = "Release notes for Canton." + + +@dataclass(frozen=True, order=True) +class Version: + major: int + minor: int + patch: int + + @classmethod + def parse(cls, value: str) -> Version: + parts = value.split(".") + if len(parts) != 3: + raise ValueError(f"Expected MAJOR.MINOR.PATCH version, got {value!r}") + return cls(*(int(part) for part in parts)) + + def __str__(self) -> str: + return f"{self.major}.{self.minor}.{self.patch}" + + +@dataclass(frozen=True) +class ReleaseNoteSource: + version: Version + path: str + blob_sha: str + + +@dataclass(frozen=True) +class PageUpdate: + previous_versions: tuple[str, ...] + current_versions: tuple[str, ...] + source_paths: tuple[str, ...] + changed: bool + + +def github_request_json(url: str) -> Any: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": USER_AGENT, + "X-GitHub-Api-Version": "2022-11-28", + } + token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") + if token: + headers["Authorization"] = f"Bearer {token}" + + request = urllib.request.Request(url, headers=headers) + try: + with urllib.request.urlopen(request, timeout=30) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as error: + message = error.read().decode("utf-8", errors="replace") + raise RuntimeError(f"GitHub API request failed for {url}: HTTP {error.code}: {message}") from error + + +def github_api_url(source_repo: str, path: str, *, query: str = "") -> str: + suffix = f"?{query}" if query else "" + return f"{GITHUB_API}/repos/{source_repo}/{path}{suffix}" + + +def release_note_sources( + *, + source_repo: str, + source_ref: str, + version_prefix: str | None, +) -> tuple[ReleaseNoteSource, ...]: + payload = github_request_json( + github_api_url(source_repo, "contents/release-notes", query=urllib.parse.urlencode({"ref": source_ref})) + ) + if not isinstance(payload, list): + raise RuntimeError(f"Expected release-notes directory listing from {source_repo}@{source_ref}") + + sources: list[ReleaseNoteSource] = [] + for item in payload: + if not isinstance(item, dict): + continue + name = item.get("name") + sha = item.get("sha") + path = item.get("path") + if not isinstance(name, str) or not isinstance(sha, str) or not isinstance(path, str): + continue + match = RELEASE_NOTE_FILE_RE.match(name) + if match is None: + continue + version = Version.parse(match.group("version")) + if version_prefix is not None and not str(version).startswith(f"{version_prefix}."): + continue + sources.append(ReleaseNoteSource(version=version, path=path, blob_sha=sha)) + + if not sources: + prefix_text = f" matching {version_prefix}" if version_prefix else "" + raise RuntimeError(f"No Canton release-note sources{prefix_text} found in {source_repo}@{source_ref}") + return tuple(sorted(sources, key=lambda source: source.version)) + + +def latest_release_line(sources: Sequence[ReleaseNoteSource]) -> str: + if not sources: + raise ValueError("Cannot select a release line from an empty source list") + latest = sources[-1].version + return f"{latest.major}.{latest.minor}" + + +def selected_release_note_sources( + *, + source_repo: str, + source_ref: str, + version_prefix: str | None, +) -> tuple[ReleaseNoteSource, ...]: + sources = release_note_sources(source_repo=source_repo, source_ref=source_ref, version_prefix=None) + selected_prefix = version_prefix or latest_release_line(sources) + selected_sources = tuple(source for source in sources if str(source.version).startswith(f"{selected_prefix}.")) + if not selected_sources: + raise RuntimeError(f"No Canton release-note sources matching {selected_prefix} found") + return selected_sources + + +def fetch_release_note_markdown( + *, + source_repo: str, + source_ref: str, + source_path: str, +) -> str: + payload = github_request_json( + github_api_url(source_repo, f"contents/{source_path}", query=urllib.parse.urlencode({"ref": source_ref})) + ) + if not isinstance(payload, dict): + raise RuntimeError(f"Expected file payload for {source_repo}@{source_ref}:{source_path}") + content = payload.get("content") + encoding = payload.get("encoding") + if not isinstance(content, str) or encoding != "base64": + raise RuntimeError(f"Expected base64 file content for {source_repo}@{source_ref}:{source_path}") + return base64.b64decode(content).decode("utf-8") + + +def current_release_version(page_text: str) -> str: + match = RELEASE_HEADING_RE.search(page_text) + if match is None: + raise ValueError("Could not find '# Release of Canton X.Y.Z' heading in release-note page") + return match.group("version") + + +def release_versions(text: str) -> tuple[str, ...]: + versions = [match.group("version") for match in RELEASE_HEADING_RE.finditer(text)] + versions.extend(match.group("slug").replace("-", ".") for match in CANTON_RELEASE_LINK_RE.finditer(text)) + return tuple(dict.fromkeys(versions)) + + +def normalized_release_markdown(markdown: str) -> str: + body = markdown.strip() + current_release_version(body) + return escape_mdx_angle_placeholders(body) + + +def escape_mdx_angle_placeholders(markdown: str) -> str: + escaped_lines: list[str] = [] + in_fence = False + for line in markdown.splitlines(): + if line.lstrip().startswith("```"): + in_fence = not in_fence + escaped_lines.append(line) + continue + if in_fence: + escaped_lines.append(line) + continue + parts = line.split("`") + for index in range(0, len(parts), 2): + parts[index] = ANGLE_PLACEHOLDER_RE.sub(r"<\1>", parts[index]) + parts[index] = parts[index].replace("-->", "-->") + escaped_lines.append("`".join(parts)) + return "\n".join(escaped_lines) + + +def rewrite_release_page(page_text: str, release_markdown: str) -> str: + frontmatter_end = page_text.find("---", 3) + if not page_text.startswith("---\n") or frontmatter_end == -1: + raise ValueError("Expected release-note index page to start with YAML frontmatter") + return f"{page_text[: frontmatter_end + 3].rstrip()}\n\n{normalized_release_markdown(release_markdown)}\n" + + +def release_slug(version: Version) -> str: + return str(version).replace(".", "-") + + +def release_page_ref(source: ReleaseNoteSource) -> str: + return f"{CANTON_RELEASE_PAGE_ROOT}/{release_slug(source.version)}" + + +def release_page_path(release_dir: Path, source: ReleaseNoteSource) -> Path: + return release_dir / f"{release_slug(source.version)}.mdx" + + +def release_page_frontmatter(source: ReleaseNoteSource) -> str: + version = str(source.version) + return ( + "---\n" + f'title: "{version}"\n' + f'description: "Canton {version} release notes."\n' + "---\n\n" + ) + + +def release_index_markdown(sources: Sequence[ReleaseNoteSource]) -> str: + newest_first = tuple(reversed(sources)) + lines = [ + "---", + f'title: "{CANTON_PAGE_TITLE}"', + f'description: "{CANTON_PAGE_DESCRIPTION}"', + "---", + "", + "{/* Generated from upstream digital-asset/canton release-note sources. */}", + "", + "Canton release notes are reproduced below from the upstream `digital-asset/canton` release-note sources.", + "", + "## Releases", + "", + ] + for source in newest_first: + version = str(source.version) + lines.append( + f"- [Canton {version}](/global-synchronizer/release-notes/canton-releases/{release_slug(source.version)})" + ) + return "\n".join(lines) + "\n" + + +def read_source_markdown( + *, + source_repo: str, + source_ref: str, + sources: Sequence[ReleaseNoteSource], +) -> dict[ReleaseNoteSource, str]: + return { + source: fetch_release_note_markdown(source_repo=source_repo, source_ref=source_ref, source_path=source.path) + for source in sources + } + + +def write_release_pages( + *, + release_index: Path, + release_dir: Path, + legacy_release_page: Path, + legacy_release_dir: Path, + sources: Sequence[ReleaseNoteSource], + source_markdown: dict[ReleaseNoteSource, str], + dry_run: bool, +) -> bool: + desired_files: dict[Path, str] = { + release_index: release_index_markdown(sources), + } + for source in sources: + desired_files[release_page_path(release_dir, source)] = ( + release_page_frontmatter(source) + normalized_release_markdown(source_markdown[source]) + "\n" + ) + + existing_release_files = set(release_dir.glob("*.mdx")) if release_dir.exists() else set() + stale_files = existing_release_files - set(desired_files) + if legacy_release_page != release_index and legacy_release_page.exists(): + stale_files.add(legacy_release_page) + if legacy_release_dir != release_dir and legacy_release_dir.exists(): + stale_files.update(legacy_release_dir.glob("*.mdx")) + changed = any(path.read_text(encoding="utf-8") != text if path.exists() else True for path, text in desired_files.items()) + changed = changed or bool(stale_files) + + if dry_run or not changed: + return changed + + release_dir.mkdir(parents=True, exist_ok=True) + for path, text in desired_files.items(): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + for path in stale_files: + path.unlink() + return True + + +def replace_canton_nav_entry(pages: list[object], page_refs: Sequence[str]) -> None: + canton_group = {"group": "Canton", "pages": list(page_refs)} + insert_at: int | None = None + filtered: list[object] = [] + for index, item in enumerate(pages): + if isinstance(item, str) and item == CANTON_PAGE_REF: + insert_at = len(filtered) + continue + if isinstance(item, str) and ( + item.startswith(f"{CANTON_PAGE_REF}/") or item.startswith(f"{CANTON_RELEASE_PAGE_ROOT}/") + ): + insert_at = len(filtered) + continue + if isinstance(item, dict) and item.get("group") == "Canton": + insert_at = len(filtered) + continue + filtered.append(item) + if item == "global-synchronizer/release-notes/splice": + insert_at = len(filtered) + if insert_at is None: + insert_at = len(filtered) + filtered.insert(insert_at, canton_group) + pages[:] = filtered + + +def update_docs_json(*, docs_json: Path, page_refs: Sequence[str], dry_run: bool) -> bool: + docs = json.loads(docs_json.read_text(encoding="utf-8")) + products = docs.get("navigation", {}).get("products") + if not isinstance(products, list): + raise ValueError(f"{docs_json} must define navigation.products") + + for product in products: + if not isinstance(product, dict): + continue + if product.get("product") == "Global Synchronizer": + groups = product.get("groups") + if isinstance(groups, list): + for group in groups: + if isinstance(group, dict) and group.get("group") == "Release Notes": + pages = group.get("pages") + if isinstance(pages, list): + replace_canton_nav_entry(pages, page_refs) + if product.get("product") == "Release Notes": + pages = product.get("pages") + if isinstance(pages, list): + for group in pages: + if isinstance(group, dict) and group.get("group") == "Canton Network": + group_pages = group.get("pages") + if isinstance(group_pages, list): + replace_canton_nav_entry(group_pages, page_refs) + + updated = json.dumps(docs, indent=2) + "\n" + before = docs_json.read_text(encoding="utf-8") + if before == updated: + return False + if not dry_run: + docs_json.write_text(updated, encoding="utf-8") + return True + + +def update_release_page( + *, + release_index: Path, + release_dir: Path, + legacy_release_page: Path, + legacy_release_dir: Path, + docs_json: Path, + source_repo: str, + source_ref: str, + version_prefix: str | None, + dry_run: bool, +) -> PageUpdate: + previous_page = release_index if release_index.exists() else legacy_release_page + previous_text = previous_page.read_text(encoding="utf-8") if previous_page.exists() else "" + previous_versions = release_versions(previous_text) + sources = selected_release_note_sources( + source_repo=source_repo, + source_ref=source_ref, + version_prefix=version_prefix, + ) + source_markdown = read_source_markdown( + source_repo=source_repo, + source_ref=source_ref, + sources=sources, + ) + changed_pages = write_release_pages( + release_index=release_index, + release_dir=release_dir, + legacy_release_page=legacy_release_page, + legacy_release_dir=legacy_release_dir, + sources=sources, + source_markdown=source_markdown, + dry_run=dry_run, + ) + page_refs = [CANTON_PAGE_REF, *(release_page_ref(source) for source in reversed(sources))] + changed_nav = update_docs_json(docs_json=docs_json, page_refs=page_refs, dry_run=dry_run) + return PageUpdate( + previous_versions=previous_versions, + current_versions=tuple(str(source.version) for source in sources), + source_paths=tuple(source.path for source in sources), + changed=changed_pages or changed_nav, + ) + + +def parse_args(argv: Iterable[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Update the published Canton release-note page from upstream.") + parser.add_argument("--source-repo", default=DEFAULT_SOURCE_REPO) + parser.add_argument("--source-ref", default=DEFAULT_SOURCE_REF) + parser.add_argument( + "--version-prefix", + help="Restrict source selection to a release line such as 3.5. Defaults to the latest upstream release line.", + ) + parser.add_argument("--release-index", type=Path, default=DEFAULT_RELEASE_INDEX) + parser.add_argument("--release-dir", type=Path, default=DEFAULT_RELEASE_DIR) + parser.add_argument("--legacy-release-page", type=Path, default=DEFAULT_LEGACY_RELEASE_PAGE) + parser.add_argument("--legacy-release-dir", type=Path, default=DEFAULT_LEGACY_RELEASE_DIR) + parser.add_argument("--docs-json", type=Path, default=DEFAULT_DOCS_JSON) + parser.add_argument("--dry-run", action="store_true") + return parser.parse_args(tuple(argv)) + + +def main(argv: Iterable[str] = sys.argv[1:]) -> int: + args = parse_args(argv) + update = update_release_page( + release_index=args.release_index, + release_dir=args.release_dir, + legacy_release_page=args.legacy_release_page, + legacy_release_dir=args.legacy_release_dir, + docs_json=args.docs_json, + source_repo=args.source_repo, + source_ref=args.source_ref, + version_prefix=args.version_prefix, + dry_run=args.dry_run, + ) + action = "Would update" if args.dry_run and update.changed else "Updated" if update.changed else "Already current" + previous = ", ".join(update.previous_versions) or "none" + current = ", ".join(update.current_versions) + print( + f"{action} Canton release notes: " + f"{previous} -> {current} " + f"from {args.source_repo}@{args.source_ref}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/update_generated_reference_prs.py b/scripts/update_generated_reference_prs.py index 5bea1e015..4fbdcc4e8 100644 --- a/scripts/update_generated_reference_prs.py +++ b/scripts/update_generated_reference_prs.py @@ -17,7 +17,6 @@ REPO_ROOT = Path(__file__).resolve().parents[1] NETWORK_VARIABLE_TAB_PAGES = ( "docs-main/appdev/deep-dives/token-standard.mdx", - "docs-main/global-synchronizer/canton-console/console-overview.mdx", "docs-main/global-synchronizer/deployment/kubernetes-deployment.mdx", "docs-main/global-synchronizer/deployment/onboarding-process.mdx", "docs-main/global-synchronizer/deployment/required-network-parameters.mdx", @@ -27,7 +26,6 @@ "docs-main/global-synchronizer/deployment/validator-kubernetes.mdx", "docs-main/global-synchronizer/production-operations/validator-disaster-recovery.mdx", "docs-main/global-synchronizer/reference/canton-console-reference.mdx", - "docs-main/global-synchronizer/understand/local-testing.mdx", "docs-main/sdks-tools/api-reference/splice-daml-apis.mdx", "docs-main/sdks-tools/api-reference/splice-http-apis.mdx", "docs-main/sdks-tools/api-reference/splice-scan-bulk-data-api.mdx", @@ -356,6 +354,27 @@ class UpdateTarget: "git diff --check", ), ), + UpdateTarget( + key="canton-release-notes", + title="Update Canton release notes", + branch="release-notes/canton/update", + description=( + "Updates the published Canton release-note page from the latest stable " + "digital-asset/canton release note in `release-notes/*.md`." + ), + generate_commands=(("nix-shell", "--run", "npm run update:canton-release-notes"),), + paths=( + "docs-main/docs.json", + "docs-main/global-synchronizer/release-notes", + ), + summary_kind="static", + summary_path=None, + summary_label=None, + validation=( + "npm run update:canton-release-notes", + "git diff --check", + ), + ), ) @@ -428,6 +447,14 @@ def summarize_target_changes(target: UpdateTarget, before_path: Path) -> list[st ) if target.summary_kind == "static": return [] + if target.summary_kind == "canton-release-notes": + if target.summary_label is None: + raise ValueError(f"Update target {target.key} must define summary_label") + return summarize_version_changes.canton_release_note_changes( + before_path, + after_path, + label=target.summary_label, + ) raise ValueError(f"Unknown summary kind for {target.key}: {target.summary_kind}") diff --git a/tests/test_update_canton_release_notes.py b/tests/test_update_canton_release_notes.py new file mode 100644 index 000000000..bf11fbad5 --- /dev/null +++ b/tests/test_update_canton_release_notes.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path +from types import ModuleType + + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def load_script_module() -> ModuleType: + script_path = REPO_ROOT / "scripts" / "update_canton_release_notes.py" + scripts_dir = str(script_path.parent) + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + spec = importlib.util.spec_from_file_location(script_path.stem, script_path) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + sys.modules[script_path.stem] = module + spec.loader.exec_module(module) + return module + + +def test_selected_release_note_sources_default_to_latest_release_line(monkeypatch) -> None: + module = load_script_module() + + monkeypatch.setattr( + module, + "github_request_json", + lambda _url: [ + {"name": "3.5.6.md", "path": "release-notes/3.5.6.md", "sha": "sha-356"}, + {"name": "3.5.10.md", "path": "release-notes/3.5.10.md", "sha": "sha-3510"}, + {"name": "3.6.1.md", "path": "release-notes/3.6.1.md", "sha": "sha-361"}, + {"name": "README.md", "path": "release-notes/README.md", "sha": "sha-readme"}, + ], + ) + + sources = module.selected_release_note_sources( + source_repo="digital-asset/canton", + source_ref="main", + version_prefix=None, + ) + + assert [str(source.version) for source in sources] == ["3.6.1"] + assert [source.path for source in sources] == ["release-notes/3.6.1.md"] + + +def test_selected_release_note_sources_can_filter_release_line(monkeypatch) -> None: + module = load_script_module() + + monkeypatch.setattr( + module, + "github_request_json", + lambda _url: [ + {"name": "3.5.6.md", "path": "release-notes/3.5.6.md", "sha": "sha-356"}, + {"name": "3.6.1.md", "path": "release-notes/3.6.1.md", "sha": "sha-361"}, + ], + ) + + sources = module.selected_release_note_sources( + source_repo="digital-asset/canton", + source_ref="main", + version_prefix="3.5", + ) + + assert [str(source.version) for source in sources] == ["3.5.6"] + + +def test_release_index_markdown_links_each_release_newest_first() -> None: + module = load_script_module() + sources = ( + module.ReleaseNoteSource(module.Version.parse("3.5.4"), "release-notes/3.5.4.md", "sha-354"), + module.ReleaseNoteSource(module.Version.parse("3.5.6"), "release-notes/3.5.6.md", "sha-356"), + ) + + assert module.release_index_markdown(sources) == """--- +title: "Canton" +description: "Release notes for Canton." +--- + +{/* Generated from upstream digital-asset/canton release-note sources. */} + +Canton release notes are reproduced below from the upstream `digital-asset/canton` release-note sources. + +## Releases + +- [Canton 3.5.6](/global-synchronizer/release-notes/canton-releases/3-5-6) +- [Canton 3.5.4](/global-synchronizer/release-notes/canton-releases/3-5-4) +""" + + +def test_normalized_release_markdown_escapes_mdx_angle_placeholders_outside_code() -> None: + module = load_script_module() + markdown = """# Release of Canton 3.5.6 + +Text with , 3.25.5 --> 3.25.9, and `.config`. + +``` +canton.participants..config +``` +""" + + assert module.normalized_release_markdown(markdown) == """# Release of Canton 3.5.6 + +Text with <filter>, 3.25.5 --> 3.25.9, and `.config`. + +``` +canton.participants..config +```""" + + +def test_update_release_page_writes_index_pages_and_nav(monkeypatch, tmp_path: Path) -> None: + module = load_script_module() + release_index = tmp_path / "docs-main" / "global-synchronizer" / "release-notes" / "canton.mdx" + release_dir = tmp_path / "docs-main" / "global-synchronizer" / "release-notes" / "canton-releases" + legacy_release_dir = tmp_path / "docs-main" / "global-synchronizer" / "release-notes" / "canton" + legacy_release_page = legacy_release_dir / "index.mdx" + docs_json = tmp_path / "docs-main" / "docs.json" + release_index.parent.mkdir(parents=True) + release_index.write_text( + """--- +title: "Canton" +--- + +# Release of Canton 3.5.3 + +Old. +""", + encoding="utf-8", + ) + legacy_release_dir.mkdir(parents=True) + legacy_release_page.write_text("stale index\n", encoding="utf-8") + (legacy_release_dir / "3-5-3.mdx").write_text("stale release\n", encoding="utf-8") + docs_json.write_text( + """{ + "navigation": { + "products": [ + { + "product": "Global Synchronizer", + "groups": [ + { + "group": "Release Notes", + "pages": [ + "global-synchronizer/release-notes/splice", + "global-synchronizer/release-notes/canton", + "global-synchronizer/release-notes/canton/3-5-3" + ] + } + ] + }, + { + "product": "Release Notes", + "pages": [ + { + "group": "Canton Network", + "pages": [ + "global-synchronizer/release-notes/splice", + "global-synchronizer/release-notes/canton", + "global-synchronizer/release-notes/canton/3-5-3" + ] + } + ] + } + ] + } +} +""", + encoding="utf-8", + ) + sources = ( + module.ReleaseNoteSource(module.Version.parse("3.5.4"), "release-notes/3.5.4.md", "sha-354"), + module.ReleaseNoteSource(module.Version.parse("3.5.6"), "release-notes/3.5.6.md", "sha-356"), + ) + monkeypatch.setattr(module, "selected_release_note_sources", lambda **_kwargs: sources) + monkeypatch.setattr( + module, + "read_source_markdown", + lambda **_kwargs: { + sources[0]: "# Release of Canton 3.5.4\n\nRelease 354.\n", + sources[1]: "# Release of Canton 3.5.6\n\nRelease 356.\n", + }, + ) + + update = module.update_release_page( + release_index=release_index, + release_dir=release_dir, + legacy_release_page=legacy_release_page, + legacy_release_dir=legacy_release_dir, + docs_json=docs_json, + source_repo="digital-asset/canton", + source_ref="main", + version_prefix=None, + dry_run=False, + ) + + assert update.previous_versions == ("3.5.3",) + assert update.current_versions == ("3.5.4", "3.5.6") + assert update.changed is True + assert not legacy_release_page.exists() + assert not (legacy_release_dir / "3-5-3.mdx").exists() + assert "/global-synchronizer/release-notes/canton-releases/3-5-6" in release_index.read_text(encoding="utf-8") + assert (release_dir / "3-5-4.mdx").read_text(encoding="utf-8").startswith( + '---\ntitle: "3.5.4"\ndescription: "Canton 3.5.4 release notes."\n---\n\n# Release of Canton 3.5.4' + ) + assert "# Release of Canton 3.5.6\n\nRelease 356.\n" in (release_dir / "3-5-6.mdx").read_text( + encoding="utf-8" + ) + docs = docs_json.read_text(encoding="utf-8") + assert '"group": "Canton"' in docs + assert '"global-synchronizer/release-notes/canton"' in docs + assert '"global-synchronizer/release-notes/canton-releases/3-5-6"' in docs + assert '"global-synchronizer/release-notes/canton/3-5-3"' not in docs diff --git a/tests/test_update_generated_reference_prs.py b/tests/test_update_generated_reference_prs.py index 2ab0854cb..0834a42e7 100644 --- a/tests/test_update_generated_reference_prs.py +++ b/tests/test_update_generated_reference_prs.py @@ -44,6 +44,7 @@ def test_update_targets_cover_all_generated_doc_surfaces() -> None: "daml-standard-library", "typescript-bindings", "canton-metrics-reference", + "canton-release-notes", ] @@ -186,6 +187,7 @@ def test_generated_clean_paths_include_target_paths_and_internal_output() -> Non assert "docs-main/snippets/generated/version-dashboard-data.mdx" in clean_paths assert "docs-main/global-synchronizer/deployment/validator-kubernetes.mdx" in clean_paths assert "docs-main/global-synchronizer/reference/canton-metrics.mdx" in clean_paths + assert "docs-main/global-synchronizer/release-notes" in clean_paths def test_target_paths_exist_in_base_checkout() -> None: