What are contract keys good for, without guaranteed uniqueness?

I’m reading the Contract Keys in Canton chapter of the docs.

The main message of the chapter for me is that “contract key uniqueness will soon be deprecated, as uniqueness can not be enforced among multiple domains. We encourage to build your models already anticipating this change”.

From which follows the question: what are contract keys good for, without guaranteed uniqueness?

You write that contract keys have the following two functions:

  • Simplifying the modeling of mutable state in Daml.

Daml contracts are immutable and can be only created and archived. Mutating a contract C is modeled by archiving C and creating a new contract C' which is a modified version of C . Other than keys, Daml offers no means to capture the relation between C and C' . After archiving C , any contract D that contains the contract ID of C is left with a dangling reference. This makes it cumbersome to model mutable state that is split across multiple contracts. Keys provide mutable references in Daml; giving C and C' the same key K allows D to store K as a reference that will start pointing to C' after archiving C .

  • Checking that no active contract with a given key exists at some point in time.

This mainly serves to provide uniqueness guarantees, which are useful in many cases. One is that they can serve to de-duplicate data coming from external sources. Another one is that they allow “natural” mutable references, e.g., referring to a user by their username or e-mail.

You also write that “In non-unique-keys mode, contract keys in Canton provide the first, but not the second function”.

I don’t understand how contract keys, without guaranteed uniqueness, can be useful for the modelling of mutable state. State representation, in any meaningful sense of the word, must be unique.

I also don’t understand, how you can use fetchByKey commands in the Workarounds for Recovering Uniqueness section while the behavior of fetchByKey is undefined:

“When evaluated against an active contract set, a fetchByKey k may result in a Fetch c action for any active contract c with the key k (in Canton, there can be multiple such contracts).”

1 Like

Contract keys represent references to a piece of mutable state maintained by the maintainers of the contract key. The operations on Daml contracts with keys correspond to the conventional shared-memory programming model as follows:

  • Creating a contract afresh with contract key k corresponds to allocating the mutable state and obtaining the reference k.
  • Archiving a contract with a key and then recreating a new Daml contract with the same key in the same transaction corresponds to a state update.
  • Archiving a contract with a key without recreating a contract with the same key corresponds to freeing the mutable state.

The idea with non-unique key mode is that the application developer makes sure that there will only ever be one such allocation for each key. Then you don’t get into the problem of several contracts for the same key k. In such a setting, all state updates are fine because the archival and the recreation in the same Daml transaction execute atomically: no other concurrent transaction can observe an intermediate state in which several contracts with the same key would be active. Therefore, fetchByKey is again well-defined: since there is only one active contract for the given key, this one contract will be fetched.

So it is enough to be careful with allocations of the mutable state. The workarounds in the documentation show a few ways how one can tackle this problem; essentially you must manually synchronize the processes that allocate state. In the simplest case, you can design your workflow such that it is always the same party that does that - then this party is responsible for ensuring uniqueness, and it can use the command deduplication features to deal with network outages and connectivity problems that might otherwise lead to several allocations happening for the same key.

3 Likes

Thanks, Andreas. This summary is the “glass half full” version of the story, the warning in the docs is the “glass half empty” version.

What I mean is that if the Daml / application developer cannot guarantee key uniqueness, or doesn’t want to take the risk of this, then it’s no use applying contract keys. Your answer confirms this for me.

I’m thinking about an unorthodox approach: using contract ids as references, making sure that the choices which update a contract, in an atomic transaction update the stored contract id reference to that contract. This seems to be easier to test and the risk of a failure, which is a dangling reference, is less than unpredictable ledger behavior. What do you think about this?