Duplicate Key Prevention across active and archived contracts

Say I have a template ContractX with key k: K , what’s the recommended approach to prevent any duplicates of ContractX being created across both active and archived contracts of type ContractX?

Approach 1 - Never archive contracts of type ContractX - so Daml’s duplicate key prevention mechanism will prevent any duplicates being created.

Approach 2 - Every time you create a contract of type ContractX, also create a contract of type ContractY with key k: K in the same transaction - archive contracts of type ContractX when needed but never archive contracts of type ContractY. Ensure ContractX can never be created without creating a ContractY in the same tx

Any other approaches? What’s the recommended approach?

1 Like

Both approaches rely on key uniqueness, which will become less strict over time because of two upcoming features:

  • multi-domain applications: as the application can run on network of domains, there’s no way to make contract keys unique across systems (docs)
  • interfaces: although it will not be possible from the first release, enhancing key operations by allowing to interact with contracts by key through their interface is already on the roadmap – uniqueness in this case is not possible because by design you might have interface instances for two contracts sharing a common interface key

Furthermore, both your approaches rely on an ever-growing set of contracts which might become untenable in a production scenario. The second approach can make retained contracts smaller, but also means that if you want to perform operations by key you’ll need to make your logic more complex, since if you want to perform operations by key on the underlying “proxied” contracts you will need to make it part of the “proxies” and just as well you will need to treat the case in which a positive lookup on the proxy is not really backed by the actual active contract. If this applies to a very limited number of contracts, you might want to consider the first option. If you expect the active contract set to grow uncontrollably because of this, neither option works. And either way, you’ll probably not be able to rely on contract key uniqueness at all because of the reasons mentioned above.

I don’t think there’s a reliable, generic way of dealing with uniqueness in this sense, especially because contract keys were design with the specific intent of being recycled as they represented a stable lookup key for related states represented by immutable contracts. There might be a way in the context of your specific application. Why do you need “historical uniqueness”?

Thanks for the explanation @stefanobaghino-da!

Assuming single-domain and no interfaces, I’m trying to understand why the Approach 2 might not work.
Let’s say I want to prevent duplicate Asset contracts. Assuming each Asset is uniquely identified by (issuer, assetId), if I were to ensure in my Daml code that:

  1. Every time I create an Asset contract, I also create an AssetId contract atomically, both contracts with the same key (issuer, assetId). Neither of the two contracts are independently creatable.
  2. There are no choices on an AssetId, all the business logic is on Asset contracts.
  3. Once created, an AssetId can never be archived for business logic purposes (it can only be archived for operational purposes such as pruning based on my pruning policy & how long I want to maintain historical uniqueness for).
  4. An Asset contract can be freely archived without touching the corresponding AssetId contract.

^With reference to this example, what do you see as the issues with managing the business logic complexity? (assuming single-domain and no usage of interfaces)

That approach should work. The main issues are:

  1. Growing your ACS by keeping the AssetId contracts active for longer has performance implications. Make sure you benchmark early to check that the window within which you want to keep uniqueness is not too large to make that feasible.
  2. It is easy to create an Asset contract somewhere and forget to create the corresponding AssetId contract somewhere. You can somewhat encapsulate that by creating some helper function that creates both but nothing enforces that all creates go through that.

Great, thanks @cocreature!

If I were to make the requirement more specific: “no duplicate asset creation requests should be accepted from any issuer”, then could I solve that by setting the CommandId of my create commands to (issuer, assetId)? That way, ensuring that the ChangeId (submitting parties, applicationId, commandId) combination will enable the ledger to auto-reject duplicate asset creation commands originating from the same issuer, based on the mechanism described here?

That only works within the deduplication period.

1 Like

Makes sense, thanks @cocreature and @stefanobaghino-da!