Why am I receiving an InconsistentKeys error instead of a DuplicateKeys for ExerciseByKey via json api?

Hi everyone!

As per this thread I’m trying to get a better understanding of the error message received after validating contract keys.

I am currently sending the same command twice, waiting in between for command completion. Instead of seeing the expected DuplicateKeys error, I am instead seeing InconsistentKeys.

I am trying to ensure proper error handling in my application, but as I cannot understand why I am not getting DuplicateKeys I cannot manage the response well.

Thanks in advance!

1 Like

To clarify, what does the command you are sending doing? Is it just creating a new contract twice, both times with the same key? Or is some contract being consumed in the process as well?

1 Like

There are multiple contracts being consumed, but all are created with new values (hence it is possible to race here, but I wait to ensure no race), and then a final contract is created as a final result. The choice itself is nonconsuming, it’s all secondary contracts being fetched, archived, and created again.

1 Like

Hi @lashenhurst,

currently InconsistentKeys is produced when contract keys referenced in a transaction have changed between submission and commit. This shouldn’t happen in your case though, if I understand correctly, as you are waiting for the first transaction to complete before sending the second one that could cause keys contention.

Is there a minimal example you can share that reproduces the condition you describe?

1 Like

The closest example would be something like:

template Vendor
   networkOwner: Party
   vendor: Party
   vendorId: Text
   signatory networkOwner
   observer vendor

   key (networkOwner, vendorId): (Party, Text)
   maintainer key._1

   controller networkOwner can
     nonconsuming RegisterProduct: (Optional (ContractId Client), ContractId Product)
       with productInfo: ProductInformation; clientInfo: ClientInformation
         clientCid <- lookupByKey @Client (clientKey)
         productCid <- lookupByKey @Product (productKey)
         assertMsg "Product already registered" (isNone productCid) !!! Should have stopped here but productKey had a bug !!!

         newClient <- case clientCid of
            None -> do
                exerciseByKey @ClientList AddClients with newClients = [clientInfo]
                case clientInfo.clientType of
                  FIRSTTYPE -> do 
                      newClient <- create Client with ...
                      return (Some newClient)
                  SECONDTYPE -> do return None
            Some clientCid -> do
                client <- fetch clientCid
                archive clientCid
                newClient <- create client with products = dedup (productInfo :: client.products) 
                return (Some newClient)
        product <- create Product with ...

        return (newClient, product)

Thanks for sharing the example; in the model, multiple concurrent transactions that exercise RegisterProduct could contend for a client key if they update (i.e., archive/re-create) the same client.

Is that right that your application prevents that from happening by submitting at most one such transaction per client, then waits for it to complete before submitting another one? If that’s not the case, then two transactions could interleave as follows:

  1. T1 looks up a client C by key.
  2. T2 looks up the same client C by key.
  3. T2 successfully adds a new product to C that becomes C’.
  4. T1 tries to add a new product to C too: when updating C, it expects the key to point to C but, due to T2, it now points to C’ instead and thus T1 is rejected with InconsistentKeys.
1 Like

Yeah, you’re exactly right. To debug this issue I’m submitting the commands manually, ensuring the first has completed and my app has the ledger response before sending the second. We shouldn’t be getting InconsistentKeys but we are anyway.

1 Like

This feels like a potential bug to me.

Would you mind opening a GitHub issue at digital-asset/daml? We’ll investigate and report back there (and here once clarified).


1 Like

Hi @lashenhurst, daml 1.13.0-snapshot.20210426.6770.0.ca66061b should fix the issue. Could you give it a try?


Hi @fabio.tudone , unfortunately I’m not able to do this due to the security restrictions in the environment where this code sits, snapshots are viewed with extreme prejudice! I would probably have to wait until 1.13.0 release to give it a try.

But I’m glad I found an actual bug, and that it was resolved, thanks for following up!