Trying to build some triggers which act not based on “events” but more focused on the heartbeat. The idea was to have the trigger execute fully once a day. However, under its logic several exercises are being executed and get in flight. One of these exercises would create a contract which would lock any other trigger executions from the get go (the trigger validates whether that contract exists for the currentDate and if it does, it does not continue).
However, we’ve witnessed a lack of order guarantee on those async calls. Therefore, it is possible for another contract to initialise the trigger and the contract which would block its execution is still not persisted.
We tried to figure out a couple of solutions:
- Guarantee no commands are in flight, therefore all is persisted (works but it’s not a pretty or scalable solution)
- Check for commands in flight and whether the specific command that we need persisted is still in flight
(that command creates the control contract for the currentDate and the trigger checks for its existence to continue or not)
This second solution would be more adjusted, however we’re having an issue figuring out how best to check whether the command came from a specific template and from a specific choice
Any help appreciated to get this done or other design solutions that we could approach.
4 Likes
Generally speaking, you can only observe commands in flight that were submitted from the same trigger instance so observing in-flight commands is not a scalable solution. Only what’s committed to the Ledger is shared “global” state that can be relied upon independent of topology.
That means you want to build your own out of band coordination layer, you need to rely on the Ledger itself to coordinate a daily task. This is demonstrated in the batch processing trigger here: ex-daml-contention/AutoBatch.daml at c1a666437907d700187a42ccabe9fc6ba108cfb7 · digital-asset/ex-daml-contention · GitHub
The basic idea is that you have a template that represents the task. That’s called BatchRequest
in this case. If you want to ensure only one such can be created per day, you could do that with contract keys, or have an umbrella contract that tracks on which day the last request was triggered, and only allows that day to move forwards.
During processing, the Request is coordinated through the RequestState
contract. Once everything is done, the trigger indicates that with a RequestDone
contract.
The nice thing is that both the overall task as well as the status / progress of the task are tracked on-ledger and kept in sync with atomic transactions. That means if something goes wrong the system as a whole is in a sound state and all you have to do is restart the trigger to continue.
3 Likes
Actually, I believe that wouldn’t be a problem as the contracts checked to understand whether the trigger can continue are to be exercised/created through the trigger itself.
Also our solution does have some of those features. Where it differs significantly is that we have multiple dedupExercise executions along the way. What we’re now thinking is to gather all these together into a single emitCommands.
1 Like