Trigger Looks like its in some loop

This is a follow up to my question about nested maps in a Trigger in this post Nested MapA for Triggers to Exercise a choice - #2 by bernhard

My terminal keeps flickering with

13:40:57.112 [TriggerRunner-akka.actor.default-dispatcher-9] WARN  com.daml.lf.engine.trigger.Runner - Command failed: DAML_AUTHORIZATION_ERROR(8,887fc896): Interpretation error: Error: node NodeId(1) (b0dd5a2d0e56041ea6673a0d0634ae85439f3259b53d7ac1ea5b2ad22026953f:Account:AssetHoldingAccount) requires authorizers b, but only a,d were given, code: 3 , context: {triggerDefinition: "b0dd5a2d0e56041ea6673a0d0634ae85439f3259b53d7ac1ea5b2ad22026953f:Trigger:autoSendExampleAssetAccountProposal"} 
13:40:57.112 [TriggerRunner-akka.actor.default-dispatcher-9] DEBUG daml.tracelog - [unknown source]: "TRIGGERED"  
^C13:40:57.117 [TriggerRunner-akka.actor.default-dispatcher-10] WARN  com.daml.lf.engine.trigger.Runner - Command failed: Channel shutdownNow invoked, code: 14 , context: {triggerDefinition: "b0dd5a2d0e56041ea6673a0d0634ae85439f3259b53d7ac1ea5b2ad22026953f:Trigger:autoSendExampleAssetAccountProposal"} 
13:40:57.118 [TriggerRunner-akka.actor.default-dispatcher-10] DEBUG daml.tracelog - [unknown source]: "TRIGGERED"  
13:40:57.118 [TriggerRunner-akka.actor.default-dispatcher-10] WARN  com.daml.lf.engine.trigger.Runner - Command failed: Channel shutdownNow invoked, code: 14 , context: {triggerDefinition: "b0dd5a2d0e56041ea6673a0d0634ae85439f3259b53d7ac1ea5b2ad22026953f:Trigger:autoSendExampleAssetAccountProposal"} 
13:40:57.119 [TriggerRunner-akka.actor.default-dispatcher-10] DEBUG daml.tracelog - [unknown source]: "TRIGGERED"  

The trigger is below. My goal is here is create an AssetHoldingAccountRequest for each new user (this is done on the frontend) , and I only want the trigger to run when there is a non-empty list of AssetHoldingAccountRequests, because I map through these requests, exercising the Accept choice. At the moment, it seems that the trigger is run in a loop.

Per the below, this template should be archived once “Accept” is exercised by the owner/admin party.

template AssetHoldingAccountRequest with
    recipient: Party
    owner: Party
  where 
    signatory recipient
    observer owner
    choice Accept: ContractId AssetHoldingAccountProposal
      with assetHoldingAccount: ContractId AssetHoldingAccount
      controller owner
      do
         exercise assetHoldingAccount Invite_New_Asset_Holder with 
          recipient = recipient

Trigger here

module Trigger where

import Account
import qualified Daml.Trigger as T
import DA.Foldable
import DA.Next.Map (Map)
import DA.Optional (whenSome)
import DA.Action
import qualified DA.List.Total as List

autoSendExampleAssetAccountProposal: T.Trigger ()
autoSendExampleAssetAccountProposal = T.Trigger 
 { initialize = pure (),
  updateState = \_  -> pure (),
  registeredTemplates = T.AllInDar,
  rule = \p -> do
    asset_holding_account_requests <- T.query @Account.AssetHoldingAccountRequest
    let isNotMe = (\requests -> requests.recipient /= p)
    let notMeList = filter (\(_, contract) -> isNotMe contract) asset_holding_account_requests
    let requests = map fst notMeList

    assetAccounts <- T.query @Account.AssetHoldingAccount
    let isET = (\account -> account.assetType.symbol == "ET")
    let etAccounts = filter (\(_, contract) -> isET contract) assetAccounts

    let cids = map fst etAccounts
    let commands = map(\request -> map(\cid -> T.exerciseCmd request Accept with assetHoldingAccount = cid) cids ) requests
    T.emitCommands ( DA.Foldable.concat commands)[]
    debug $ "TRIGGERED",
  heartbeat = None
}

(some additional context)
The lines below is actually a work around, what I actually want to do is grab 1 account with a key

 let isET = (\account -> account.assetType.symbol == "ET")
 let etAccounts = filter (\(_, contract) -> isET contract) assetAccounts

I suspect it’s triggering non stop due to the line

    let commands = map(\request -> map(\cid -> T.exerciseCmd request Accept with assetHoldingAccount = cid) cids ) requests

Any ideas? I previously was mapping over the list of Cids (the length should be always 1), and that’s why I was thinking perhaps since it was always non-empty, the trigger would keep firing.

But I since reversed the mapping, so we are mapping the requests in the outer map. So technically if all the requests were archived through “Accept” It shouldn’t keep firing right?

If you think you are duplicate-exercising due to pending consuming commands not being complete yet, the first thing to try is to pass a second argument to emitCommands rather than []. (map toAnyContractId cids) should suffice in this case.

More complex cases may require you to use a state instead of () for your trigger type. But I think this is unnecessary here, as you do not expect to be able to exercise more than one choice on a given contract, and do not expect to be able to query for it more than once.

I also suggest setting a proper value for registeredTemplates rather than AllInDar; this will cut down on spurious rule executions and make it easier to diagnose some issues.

1 Like

I tried map toAnyContractId cids), and registered the template instead of AllInDar, I still get a loop.

any idea what this line, in the error below?
snippet:

Command failed: MISSING_FIELD(8,0d2d02c0): The submitted command is missing a mandatory field: commands, code: 3 , context: {triggerDefinition: "9ee442348f756d08624e2e5eb937407a684101d0a5f0efdc9b6e5551412f1955:Trigger:autoSendExampleAssetAccountProposal"}

Full message that is on loop in the terminal

16:16:59.908 [TriggerRunner-akka.actor.default-dispatcher-5] WARN  com.daml.lf.engine.trigger.Runner - Command failed: MISSING_FIELD(8,0d2d02c0): The submitted command is missing a mandatory field: commands, code: 3 , context: {triggerDefinition: "9ee442348f756d08624e2e5eb937407a684101d0a5f0efdc9b6e5551412f1955:Trigger:autoSendExampleAssetAccountProposal"} 

This is the updated code, i tried the dedup, but that didn’t help.
I also noticed that the debug is not printing anything except for “TRIGGERED”,
debug requests, and debug cids aren’t printed.

autoSendExampleAssetAccountProposal: T.Trigger ()
autoSendExampleAssetAccountProposal = T.Trigger 
 { initialize = pure (),
  updateState = \_  -> pure (),
  registeredTemplates = T.RegisteredTemplates [T.registeredTemplate @Account.AssetHoldingAccountRequest],
  rule = \p -> do
    asset_holding_account_requests <- T.query @Account.AssetHoldingAccountRequest
    let isNotMe = (\requests -> requests.recipient /= p)
    let notMeList = filter (\(_, contract) -> isNotMe contract) asset_holding_account_requests
    let requests = map fst notMeList
    debug requests
    assetAccounts <- T.query @Account.AssetHoldingAccount
    let isET = (\account -> account.assetType.symbol == "ET")
    let etAccounts = filter (\(_, contract) -> isET contract) assetAccounts
  
    let cids = map fst etAccounts
    debug cids
    let commands = map(\request -> map(\cid -> T.exerciseCmd request Accept with assetHoldingAccount = cid) cids ) requests
    debug cids
    T.emitCommands ( DA.Foldable.concat commands)(map T.toAnyContractId requests)
    debug $ "TRIGGERED",
  heartbeat = None
}

When I comment out the T.emitCommands,
I can see in the terminal that the trigger runs properly (thought without any updates to the ledger), and there is no infinity looping.

16:30:16.427 [TriggerRunner-akka.actor.default-dispatcher-8] INFO  com.daml.lf.engine.trigger.Runner - Trigger  is running as a with readAs=[] , context: {triggerDefinition: "1d928f21b2ffdeff8de66903643d165a9086e7d7b5242141b260877ca4edf360:Trigger:autoSendExampleAssetAccountProposal"} 
16:30:16.487 [TriggerRunner-akka.actor.default-dispatcher-8] DEBUG daml.tracelog - [unknown source]: []  
16:30:16.490 [TriggerRunner-akka.actor.default-dispatcher-8] DEBUG daml.tracelog - [unknown source]: []  
16:30:16.491 [TriggerRunner-akka.actor.default-dispatcher-8] DEBUG daml.tracelog - [unknown source]: []  
16:30:16.492 [TriggerRunner-akka.actor.default-dispatcher-8] DEBUG daml.tracelog - [unknown source]: "TRIGGERED"  

The solution in this post seems to stop the loop at the start up of the trigger

But as soon as a new contract is created for AssetHoldingAccountRequest, the loop starts.

Can you share the code you are using now?

The below fixes the infinity triggering. But there is one more thing described below

autoSendExampleAssetAccountProposal: T.Trigger ()
autoSendExampleAssetAccountProposal = T.Trigger 
 { initialize = pure (),
  updateState = \_  -> pure (),
  registeredTemplates = T.RegisteredTemplates [T.registeredTemplate @AssetHoldingAccountRequest, T.registeredTemplate @AssetHoldingAccount],
  rule = \p -> do
    asset_holding_account_requests <- T.query @AssetHoldingAccountRequest
    let isNotMe = (\requests -> requests.recipient /= p)
    let notMeList = filter (\(_, contract) -> isNotMe contract) asset_holding_account_requests
    let requests = map fst notMeList


    debug ("requests",requests)
    assetAccounts <- T.query @AssetHoldingAccount
    debug ("assetAccounts", assetAccounts)
    let isET = (\account -> account.assetType.symbol == "ET" && account.assetType.issuer == p)
    let etAccounts = filter (\(_, contract) -> isET contract) assetAccounts
    let cids = map fst etAccounts
    
    
    unless ( DA.Foldable.null requests && DA.Foldable.null cids ) do
      let (cid, c) = head etAccounts

      mapA_(\request -> T.dedupExercise request Accept with assetHoldingAccount = cid)  requests
      pure()
    debug $ "TRIGGERED",
  heartbeat = None
}

There is a gotcha here that causes the trigger to fail. And that is

let (cid, c) = head etAccounts

There is a chance that the cid is empty, and that fails the trigger. So I need to ensure that I have created an AssetHoldingAccount, with account.assetType.symbol == “ET”,

Is there a way to prevent the trigger from running if that contract doesn’t exist yet?

How about something like this:

    unless ( DA.Foldable.null requests && DA.Foldable.null cids ) do
      case etAccounts of
        [] -> pure ()
        (cid, c) :: _ ->
          mapA_(\request -> T.dedupExercise request Accept with assetHoldingAccount = cid)  requests

That way your trigger simply does nothing if the list is empty.

1 Like

I also suggest using find instead of filter to define etAccounts (which would probably be etAccount then).

You also might then shift this lookup to earlier in the rule definition, using whenSome to guard the rest of the rule to only figure your requests in case the etAccount is defined.

2 Likes

Thank guys! I will give it a try.

@cocreature How to use (cid, c) :: _ in case? I want to create another template by using some values from contract c. How to extract values from c?

Hi @Pris17,

Let’s first take a quick look at what that expression means:

case someExpr of
  [] -> undefined
  (cid, c) :: _ -> undefined

This means that:

  • someExpr is an expression of type [(a, b)], i.e. a list of which the elements are tuples of two elements. From that expression alone we can’t tell anything about the element types, but from additional context we know they’re something like (ContractId c, c).
  • (cid, c) :: _ means “in the case where there is at least one element, name the first value of the pair cid and the second value c, and disregard all other elements (:: _)”.

In this case, cid is an opaque, “atomic” value, so there is no way (or need) to “dig into” it. However, c is itself a record of a type that corresponds to a template, and is thus a compound value.

There are a few ways to dig into it, but we need a slightly more concrete example to illustrate them. Let’s assume we have a template definition:

template Example
  with
    a : Text
    b : Int
    c : Party
  where
    signatory c

Now, if c happens to be of type Example, here are a few ways of accessing its values. First off, we can use so-called “dot notation” to access its fields:

usingDot : [(ContractId Example, Example)] -> Script ()
usingDot listOfContracts = case listOfContracts of
  [] -> pure ()
  (cid, c) :: _ -> do
    debug $ c.a <> " (id: " <> show c.c <> ") is " <> show c.b <> " years old"
    return ()

If you want to bind all of the fields and give them custom names, you can use the constructor directly:

usingConstructor : [(ContractId Example, Example)] -> Script ()
usingConstructor listOfContracts = case listOfContracts of
  [] -> pure ()
  (cid, Example name age id) :: _ -> do
    debug $ name <> " (id: " <> show id <> ") is " <> show age <> " years old"
    return ()

You can also use field destructuring, which lets you pick specific fields, if you don’t need all of them:

usingDestructuring : [(ContractId Example, Example)] -> Script ()
usingDestructuring listOfContracts = case listOfContracts of
  [] -> pure ()
  (cid, Example { a = name, b}) :: _  -> do
    debug $ name <> " is " <> show b <> " years old"
    return ()

Here, you can see two different syntaxes to access a field: a = name uses the record field a but renames it locally to name, whereas b here gets the field and keeps its name.

Finally, you can also bring all of the fields directly in scope, which I personally don’t like, using the splat operator ..:

usingSplat : [(ContractId Example, Example)] -> Script ()
usingSplat listOfContracts = case listOfContracts of
  [] -> pure ()
  (cid, Example {..}) :: _ -> do
    debug $ a <> " (id: " <> show c <> ") is " <> show b <> " years old"
    return ()

I’m not an expert so can’t claim this list is exhaustive, but hopefully you find at least one option you like among those.

Thanks @Gary_Verhaegen for the detailed explanation.
This helps me to understand the expression quite well and implement it accordingly. I will try out and ask for any help if I get stuck.

@Gary_Verhaegen
usingDot : [(ContractId Example, Example)] -> Script () usingDot listOfContracts = case listOfContracts of [] -> pure () (cid, c) :: _ -> do debug $ c.a <> " (id: " <> show c.c <> ") is " <> show c.b <> " years old" return ()

In above e.g. if I want to process all the elements in the list then how should be the 2nd switch written?

(cid, c) :: _ -> 

instead of _, what needs to be written and how to then process all the elements of the list?

There are multiple ways to do that, and the best approach will depend on what you’re trying to do in a slightly broader sense. Can you elaborate a little bit more on what your use-case is?