This blog post previews the upcoming Splice 0.4.0 release notes so you can plan any application changes and have time to answer any questions.
Application Changes Needed for Splice 0.4.0
Updates Needed before Splice 0.5.0
This blog post previews the upcoming Splice 0.4.0 release notes. It highlights the new features in Canton/Daml 3.3 that focus on easing application development, testing, upgrading, and supporting the long-term stability of Canton APIs. These new features may impact Canton Network applications and associated backends. So, we are giving you an early preview prior to the release so you can plan any application changes and have time to answer any questions. Application migration details are provided for guidance. The complete Daml 3.3 Release Notes will be published later to this blog site.
There are two types of application updates that are discussed below. The first changes are needed to run on a network that has migrated to Splice 0.4.0. The second set are for deprecated features that are backwards compatible in Splice 0.4.0 but the backwards compatibility will be removed in Splice 0.5.0.
PLEASE NOTE: Applications that use the Validator APIs and Scan APIs are not affected (see validator node network diagram). Applications that integrate directly with the Canton participant node and its Ledger API should review this preview to understand any changes they may need to make.
The majority of application updates are related to the Canton's Ledger API. This section provides additional context to supplement the Migration guide from version 3.2 to version 3.3.
For clarity, this document distinguishes between two JSON API versions:
More details are below in the section Canton JSON Ledger API v1 is Deprecated by Canton JSON API v2.
In Daml 3.2, the ledger offset is a string value that is usually converted to a numeric value. In Daml 3, the offset is now an int64 which allows trivial and direct comparisons. Negative values are considered to be invalid. A zero value denotes the participant’s begin offset and the first valid offset is 1. Logged offset values will not be in the current hexadecimal format but instead be a decimal Any LAPI or JSON API v1 calls will have to make this change. The String representation is replaced by Long for the Java bindings.
The events that are published by the participant node's ledger API have changed. In Daml 3.2, event IDs are strings constructed through concatenation of a transaction ID and node ID and they look something like:
#122051327f59fd759c0b16a07f4cd7146960fb7ada6bfcd56e3144f30a503f5e0010:0
The node-ids are participant node specific and are not interchangeable.
In Daml 3.3, the event_id is replaced with a pair (offset, node_id) of integers for all events, recording the origin and position of the events respectively. The current event-id is replaced with the node-id for event-bearing messages such as CreatedEvent, ArchivedEvent, ExercisedEvent. This approach reduces internal and client storage use without any loss in functionality. The lookups by event ID need to be replaced by lookups by offset. The semantics are that the transaction tree structure is recoverable from the node-ids as node-ids within a transaction carry the same information as old event-ids (discussed in Universal Event Streams)..
This is accomplished by:
The GetTransactionByEventId and GetTransactionTreeByEventId queries are removed and replaced by GetUpdateById. The GetTransactionByOffset and GetTransactionTreeByOffset queries are replaced by GetUpdateByOffset.
The migration changes are described in Event ID to offset and node_id. Any JSON API v1 calls will also have to make this change.
Currently, a Ledger API (LAPI) client can subscribe to ledger events and receive either a flat transaction stream or a transaction tree stream where neither provides a complete view. Subscribing to topology events is not available either. Universal Event Streams is a new feature that overcomes these challenges while providing additional filtering and formatting capabilities.
The Universal Event Streams feature has transaction filters and streams with the following capabilities:
It combines the topology and package information into a single continuous stream of updates ordered by their offsets. Future event types will be added in a backwards compatible manner.
The structural representation of daml transaction trees no longer exposes the root event IDs and the children for exercised events. It now exposes the last descendant node ID for each node. As a result building a transaction tree is different now. This new representation allows:
The representation can be considered a variant of the DFUDS (Depth-First Unary Degree Sequence) or a Nested Set model representation.
Furthermore, the event nodes are guaranteed to be output in Daml execution order to simplify processing them in that order. If you do need to traverse the actual tree, then encoding that traversal as a recursive function with an additional lastDescendandNodeId argument for when to stop the traversal of the current node will work well. The figures below illustrate the difference.
Daml 3.2: store all children of a node and root nodes like this:
Daml 3.3: store the highest node id of node’s descendants:
The Java bindings have an exmaple helper class that can be leveraged to reconstruct the transaction tree. There is also a helper function getRootNodeIds(). The node IDs or root nodes (i.e. the nodes that do not have any ancestors) are important for this computation. A node is a root node if there are not any ancestors of it. There is no guarantee that the root node was also a root in the original transaction (i.e. before filtering out events from the original transaction). In the case that the transaction is returned in AcsDelta shape all the events returned will trivially be root nodes.
For those changes that are required for Splice 0.4.0 see the heading “Required Changes in 3.3” in the section Universal Event Streams.
There are some cosmetic API changes that were delayed from the jump from Daml 2 to Daml 3. These naming changes are aggregated into this release.
The first is renaming the application_id in the Ledger API to user_id. The migration changes are described in Application ID to User ID rename. Any JSON API v1 calls will also have to make this change.
The second is in anticipation of multi-synchronizer applications where the term domain has changed to synchronizer. The migration changes are described in Domain to Synchronizer rename. Any JSON API v1 calls will also have to make this change.
The interactive submission service and external signing authorization logic are now always enabled. The following configuration fields must be removed from the Canton participant node's configuration:
- ledger-api.interactive-submission-service.enabled
- parameters.enable-external-authorization
The hashing algorithm for external signing has been updated from V1 to V2. Daml 3.3 will only support hashing algorithm V2 which is not backwards compatible with V1 for several reasons:
Support for V1 has been dropped and will not be supported in Canton 3.3 onward. Refer to the hashing algorithm documentation for the updated version.
This is important for applications that re-compute the V1 hash client-side. Such applications must update their implementation to V2 in order to use the interactive submission service on Canton 3.3.
Also, the following renamings have happened to better represent their contents:
Streaming and point-wise queries support for the Smart Contract Upgrade feature (discussed below) have changed.
Streaming and point-wise queries support for smart contract upgrading:
SCU allows Daml models (packages in DAR files) to be updated transparently. For example, you can fix a Daml model's bug by uploading the DAR that has the fix in a new version of the package. It was introduced in Daml v2.9.1 and is now also available in Daml 3.3.
You may need to adjust your development practice to ensure package versions are set following a semantic versioning approach. To prevent unexpected behavior, this feature enforces that a DAR being uploaded to a participant node has a unique package name and version, and that packages with the same name are upgrade-compatible. So it is not possible to upload multiple DARs with the same package name and version. Please ensure you are setting the package version in the daml.yaml files and increasing the version number as new versions are developed.
The 3.x documentation for SCU is in preparation but not yet available. However, the documentation from 2.x is largely applicable and available here with the reference documentation available here; please ignore the protocol version and the Daml-LF version details.
The Participant Query Store (PQS) is compatible with both Daml 2 and Daml 3. The only changes needed are those due to type changes in the Ledger API.
The detailed changes are as follows:
We recommend displaying the event identifying information as <offset>:<node_id>.
This simple Java client is an example of the usage.
Daml Script has the following changes:
This section describes the API features that are deprecated in Splice 0.4.0 and will require application changes before running on a network upgraded to Splice 0.5.0 slated to be released in late 2025. Additional blogs will provide more detailed migration information. Please monitor the #gsf-global-synchronizer-appdev channel for the announcements.
As mentioned, JSON Ledger API v1 is deprecated in this release and will be removed in Splice 0.5.0/Daml 3.4. So, applications need to migrate to JSON Ledger API v2 which is available in Splice 0.4.0/Daml 3.3.
Please note that JSON API v2 does not support query-by-attribute capabilities currently offered by JSON API v1. These queries have proven problematic and are no longer supported. The LAPI pointwise query APIs can be used. For more general querying capabilities, it is recommended to use the Participant Query Store (PQS).
The migration details are available in the "HTTP JSON API Migration to V2 guide". Please note that including @daml/ledger will not work for V2 because it is for Canton JSON API V1.
In Daml 3.3, Smart Contract Upgrade supported two formats for specifying interface and template identifiers to the Ledger API. They are:
The package-id reference format will not be supported in Splice 0.5.0. So applications must switch to using the package-name reference format for all requests submitted to the Ledger API (commands and queries).
The above section Universal Event Streams introduced this new feature and describes the minimal changes that are needed to work in Splice 0.4.0. To work in Splice 0.5.0, further application updates are needed because the deprecated APIs will be removed. The heading “Changes Required before the Next Major Release” in the section Universal Streams has the migration details.
The java-binding classes that wrap the deprecated Ledger API messages will be removed in the next major release:
You must thus remove your use of these deprecated classes to be ready to adopt the Splice 0.5.0 release.
Canton has a standard model for describing errors which is based on the standard gRPC description. The use of standardized LAPI error responses "enable API clients to construct centralized common error handling logic. This common logic simplifies API client applications and eliminates the need for cumbersome custom error handling code."
In Daml 3.2 there are two error handling systems:
Daml exceptions are deprecated in Daml 3.3 so please consider migrating away from them.
Daml 3.3 introduces the failWithStatus Daml method so user defined Daml errors can be directly created and passed to the ledger client. The Ledger API client then can inspect and handle errors raised by Daml code and Canton in the same fashion. This approach has several benefits for applications:
An example will help to make this concrete. Consider the following daml exception:
exception InsufficientFunds with
required : Int
provided : Int
where
message “Insufficient funds! Needed “ + show required + “ but only got “ + show provided
This would have been received by a ledger client as:
Status(
code = FAILED_PRECONDITION, // The Grpc Status code
message = "UNHANDLED_EXCEPTION(9,...): Interpretation error: Error: Unhandled Daml exception: App.Exceptions:InsufficientFunds@...{ required = 10000, provided = 7000}",
details = List(
ErrorInfoDetail(
// The canton error ID
errorCodeId = "UNHANDLED_EXCEPTION",
metadata = Map(
participant -> "...",
// The canton error category
category -> InvalidGivenCurrentSystemStateOther,
tid = "...",
definite_answer = false,
commands = ...
)
),
RequestInfo(
correlationId = "..."
),
ResourceInfo(
typ = "EXCEPTION_TYPE",
// The daml exception type name
name = "<pkgId>:App.Exceptions:InsufficientFunds"
),
ResourceInfo(
typ = "EXCEPTION_VALUE",
// The InsufficientFunds record, with “required” and “provided” fields
name = "<LF-Value>"
),
)
)
Now it will be received by the client as:
Status(
code = 9, // FAILED_PRECONDITION
message = "DAML_FAILURE(9, ...): UNHANDLED_EXCEPTION/App.Exceptions:InsufficientFunds: Insufficient funds! Needed 10000 but only got 7000",
details = List(
ErrorInfoDetail(
errorCodeId = "DAML_FAILURE",
metadata = Map(
"error_id" -> "UNHANDLED_EXCEPTION/App.Exceptions:InsufficientFunds"
"category" -> "InvalidGivenCurrentSystemStateOther",
... other canton error metadata ...
)),
...
)
)