How to debug trigger?

I am trying to use trigger to pass information from one DAML contract to another, like this:

template Bar
    with
        owner: Party
        someInfo: Int
        state: Bar_State
    where
        signatory owner
        controller owner can 
            Bar_Accept : ContractId Bar
                do
                    create BarTrigger with info=someInfo
                    create this with state=BarState_Accepted
            Bar_Reject : ContractId Bar
                do
                    create this with state=BarState_Rejected

template BarTrigger
    with
        owner: Party
        info: Int
    where
        signatory owner
        controller owner can
            BarTrigger_Consume: ()
                do pure()

The state transition of Bar will trigger the execution of DoSomething, which is defined as a choice of some other contract in another file.

triggerRule : Party -> TriggerA () ()
triggerRule _party = do
    triggers <- query @BarTrigger
    forA_ triggers $ \(cid, c) -> do
    if (c.owner == _party)
    then do
        debug ((show c.owner) <> " do something with " <> "info=" <> (show c.info))
        dedupExercise someCid DoSomething with info=c.info
        dedupExercise cid BarTrigger_Consume
    else pure()

The code works if I only have one trigger generated. If I have multiple triggers (from multiple contracts generated from the template Bar), I get this error:

Trigger  is running as alice (context: {triggerDefinition=75c51e68bcce2f98be4ce858be6ad37b26b7bb55277440fa07b61a6295434ec7:Keplaax.Triggers.SellerTrigger:trigger})
[unknown source]: "'alice' do something with info=2"
[unknown source]: "'alice' do something with info=1"
[unknown source]: "'alice' do something with info=1"
Command failed: Inconsistent: DuplicateKey: Contract Key not unique, code: 3 (context: {triggerDefinition=75c51e68bcce2f98be4ce858be6ad37b26b7bb55277440fa07b61a6295434ec7:Keplaax.Triggers.SellerTrigger:trigger})

I guess I am doing something stupid in DoSomething.

I am seeking for advice on how to debug DoSomething with Daml navigator (I guess I have to because I am using trigger?)… I added debug into DoSomething but I am not sure where the trace go.

1 Like

With the script like this:

b1Cid <- submit alice do
  create Bar with owner=alice, someInfo=1, state=BarState_Created
b2Cid <- submit alice do
  create Bar with owner=alice, someInfo=2, state=BarState_Created

submit alice do exercise b1Cid Bar_Accept
submit alice do exercise b2Cid Bar_Accept

I would get

Trigger  is running as alice (context: {triggerDefinition=75c51e68bcce2f98be4ce858be6ad37b26b7bb55277440fa07b61a6295434ec7:Keplaax.Triggers.SellerTrigger:trigger})
[unknown source]: "'alice' do something with info=2"
[unknown source]: "'alice' do something with info=1"
[unknown source]: "'alice' do something with info=1"
Command failed: Inconsistent: DuplicateKey: Contract Key not unique, code: 3 (context: {triggerDefinition=75c51e68bcce2f98be4ce858be6ad37b26b7bb55277440fa07b61a6295434ec7:Keplaax.Triggers.SellerTrigger:trigger})

I would get different result if I do not use the script to exercise Bar_Accept, I would get the expected result like this

(click Bar_Accept from Daml navigator for b1Cid)

[unknown source]: "'alice' do something with info=1"

(click Bar_Accept from Daml navigator for b2Cid)

[unknown source]: "'alice' do something with info=2"
1 Like

It’s hard to be more precise without the full code, but the error message means the trigger is trying to create a contract with a key that already exists.

DAML enforces key uniqueness: at any point in time, there can only ever be one active contract with a given key. Before you can create another contract with that key, you have to archive the existing one. You may do both in a single transaction, such that other observers never see a state of the ledger where there is no contract for the given key; in that way, contract keys provide a notion of a mutable reference to immutable values.

It is likely there is a deeper concurrency bug in the code that only appears when using triggers simply because of the time it takes to manually click. A simple, immediate fix would be to add a check to your creation code and archive the existing contract if there is one. Here is a small, self-contained example of how to do that:

import Daml.Script

template Asset
  with
    issuer: Party
    message: Text
  where
  key issuer: Party
  maintainer key
  signatory issuer

template AssetUpdater
  with
    c: Party
  where
  signatory c
  preconsuming choice UpdateMessage : ContractId Asset
    with
      newMsg: Text
    controller c
    do
      alreadyExists <- visibleByKey @Asset c
      if alreadyExists then do
        (cid, _) <- fetchByKey @Asset c
        archive cid
        create Asset with issuer = c, message = newMsg
      else do
        create Asset with issuer = c, message = newMsg


setup : Script ()
setup = script do
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  _ <- submit alice do
    createCmd Asset with
      issuer = alice
      message = "1"
  _ <- submit alice do
    createAndExerciseCmd (AssetUpdater alice) (UpdateMessage "2")
  pure ()

However, I can’t stress this enough: there is likely something wrong with the design around the use of keys and/or the concurrency semantics of the contracts, so blindly applying the above may just result in a silently broken model instead of a loudly broken one, which is strictly worse.

1 Like

Hi Gary, thanks a lot of your reply. I fully understand that the problem probably lines in my code. What I am looking for is coding style or best practice that would make bugs easier to locate… after all, bugs are part of life and they are unavoidable.

Your suggestion on adding preconsuming check into the template is an exactly what I am looking for. Now I can add these checks and when the “Duplicate Key” error happens again, I would know where it comes from.

Thanks again, and Happy New Year.

Cheers

1 Like

One thing that still puzzle me, is the duplication of the second contract (info=1):

I checked from Daml Navigator that only 2 contracts are created from the template BarTrigger.

I added some more debug to the trigger:

triggerRule _party = do
  triggers <- query @BarTrigger
  debug $ " === " <> (show triggers)
  forA_ triggers $ \(cid, c) -> do
    when (c.owner == _party) do
        debug $ "Archive the trigger with info=" <> (show c.info))
        dedupExercise cid BarTrigger_Consume

I got:

[unknown source]: " === [(<contract-id>,BarTrigger {owner = 'alice', someInfo = 2}),(<contract-id>,BarTrigger {owner = 'alice', someInfo = 1})]"
[unknown source]: "Archive the trigger with info=2"
[unknown source]: "Archive the trigger with info=1"
[unknown source]: " === [(<contract-id>,BarTrigger {owner = 'alice', someInfo = 1})]"
[unknown source]: "Archive the trigger with info=1"
[unknown source]: " === []"

I expect both triggers to be archived after a single iteration of the for loop, but look like only one archive has actually happened.

That would explain why I got DuplicateKey, because in doSomething I am creating a new contract with with the someInfo, and the key of these contracts is (owner, someInfo).

1 Like

Given the observation that not all contracts are archived in a for loop, I rewrite the trigger:

triggerRule _party = do
  triggers <- query @BarTrigger
  filteredTriggers = filter (\(_, c) -> c.owner == _party) triggers
  unless (null filteredTriggers) do
    debug ((show c.owner) <> " do something with " <> "info=" <> (show c.info)
    dedupExercise someCid DoSomething with info=c.info
    dedupExercise cid BarTrigger_Consume

I am still seeing this debug messages:

[unknown source]: "'alice' do something with info=2"
[unknown source]: "'alice' do something with info=1"
[unknown source]: "'alice' do something with info=1"

But I don’t see the DuplicateKey error any more. I got 2 new contracts created from DoSomething, and this is what I want.

It is very strange …

1 Like