Skip to main content
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.

Release of Canton 3.5.3

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. 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). The Daml Assistant (daml) was deprecated in Canton 3.4. The dpm CLI is documented here. A command migration table, from daml to dpm commands, is available here.

Daml Exception Handling Updates

Daml exception handling has been deprecated since Canton 3.3. 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 for the updated version.
  • The max_record_time is now enforced by all confirming participants.
See the versioning documentation 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.<participant>.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.<participant-id>.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 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 #<package-name>:<module>:<entity>. 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. The change is that the format of the synchronizer_id 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.<participant_name>.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. 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 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-<UUID>).
  • 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

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 <node>.topology.<mapping>.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 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, are implemented and the mechanics are documented. As an example, Digital Asset maintains a multi-synchronizer sample application “Splitwell” and continuously tests that it works in a multi-synchronizer deployment as part of CI/CD testing. 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.<participant>.ledger-api.postgres-data-source.network-timeout for network timeout of the Ledger API server and canton.participants.<participant>.parameters.ledger-api-server.indexer.postgres-data-source.client-connection-check-interval for the client_connection_check_interval of the indexer.
  • <canton-node>.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.<participant_name>.ledger-api.interactive-submission-service.maximum-number-of-signatures-per-party
  • Added a new configuration parameter canton.participants.<participant_name>.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 for details. The same simplification has been made to the Daml Shell Docker image and is described here.

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 <node>.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.<participant>.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.<participant>.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 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.<node>.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.
  • 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:
DependencyVersion
Canton protocol versions34, 35
Canton has been tested against the following versions of its dependencies:
DependencyVersion
Java RuntimeOpenJDK 64-Bit Server VM (build 21.0.10+7-nixos, mixed mode, sharing)
PostgresRecommended: 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/ 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<string>;
 /**
 * This optional field…
 */
 'beginExclusive': number;
After:
export class CompletionStreamRequest {
 /**
 * … Required unless authentication is used with a user token…
 */
 'userId'?: string;
 /**
 * … Required
 */
 'parties'?: Array<string>;
 /**
 * …. 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/ 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<String> 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<String> 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<Vec<String>>,
 /// 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<String>,
 /// …Required
 #[serde(rename = "parties", skip_serializing_if = "Option::is_none")]
 pub parties: Option<Vec<String>>,
 /// …Optional
 #[serde(rename = "beginExclusive", skip_serializing_if = "Option::is_none")]
 pub begin_exclusive: Option<i64>,
}