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.
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
I’m somewhat guessing here but I believe that cmds is probably empty. The ledger API does not accept empty list of commands. If that is the issue you can simply wrap it in when: when (not (null cmds)) (emitCommands cmds pendings) Might make sense to modify emitCommands to handle this automatically.
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.
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.
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:
-
someExpris 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 paircidand the second valuec, 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?