Daml Trigger heartbeat example

Could any of you post a simple example for a Daml Trigger which exercises a specific choice on all instances of a specific template, at the first day of every month?

If I understand correctly the heartbeat feature of Daml Triggers supports this.

1 Like

Great question!

Not exactly what you want but relevant.

From the DA_SDK.pdf, page 547/577:

data Trigger s = Trigger
{ initialize : TriggerInitializeA s
, updateState : Message -> TriggerUpdateA s ()
, rule : Party -> TriggerA s ()
, registeredTemplates : RegisteredTemplates
, heartbeat : Optional RelTime
}

From the DA_SDK.pdf, page 548/577:

copyTrigger : Trigger ()
copyTrigger = Trigger
{ initialize = pure ()
, updateState = \_message -> pure ()
, rule = copyRule
, registeredTemplates = AllInDar
, heartbeat = None
}

If you grab the DA_SDK.pdf and do a search for heartbeat there are about 10 references.

Question: If you can use a Trigger to perform an action at a determined time or on an event, is there a Daml-based mechanism to send an event or notification out of the Daml system, once a successful Trigger action has occurred?

Example:

When the price of NZ Lamb Hoggett, reaches NZD $110, trigger a purchase.
If there is a purchase, send an notification to an external service.

Is this doable?

2 Likes

Heartbeats do not allow you to fire your trigger at a specific time or time interval. What they do is they ensure liveness meaning even if there are no changes to the active contract set, your trigger will fire at least at the interval you specified. By reducing the interval you can choose how high of a delay you’re willing to accept.

So for your example, you could say you’re willing to accept a delay of a minute and set the heartbeat to that.
In your rule, you can get access to the time via getTime and check if you’re on the first day of a month and send commands in that case. Note that your trigger will run every minute on that day if you set the heartbeat to a minute so you need to write your trigger such that it only fires if the change hasn’t already been applied to the ACS.

3 Likes

That makes sense. So what mechanism do you use if you want modify the ACS based on a specific event? One that does not involve a Human interacting with the UI?

2 Likes

Can I set the heartbeat for 1 day as well?

1 Like

Can I set the heartbeat for 1 day as well?

Sure but a few ceavets:

  1. That means you have up to 24h delay meaning you won’t fire at 00:00 but at 23:59 depending on when you started your trigger.
  2. It still doesn’t ensure your trigger doesn’t fire more often. Any ACS event for the registered templates as well as completions for commands you sent from your trigger wlll fire the trigger so it doesn’t remove the need to write your trigger such that it doesn’t fire multiple times on the same day.
2 Likes

This is not the right way to think about triggers. You should not write your triggers to react to events, you should write your triggers to “correct the current state”. The logic of the trigger should essentially be along the lines of:

  • I have some expectations about rules that should be verified by the current state at all times.
  • Get the ACS, check if the rules hold.
  • If they don’t, issue commands to correct the ACS so it matches the state I expect.

As a high-level example, if you had a Daml model with a propose/accept pattern in it, you could think about this in terms of events:

Every time I receive a proposal, I accept it.

But that would be subtly wrong and lead to discrepancies in various contexts. A better way to think about it would be:

I expect the ACS to not have any unanswered proposal. I correct the state by accepting all pending proposals.

That will put you in the right frame of mind to write Daml Trigger code that meshes well with the design of both the trigger runner and the available trigger APIs.

Note that in that second approach, it does not matter whether you run the trigger too often, or at specified intervals. All that matters is the current state at the time the trigger runs, and whether that matches your expectations.

6 Likes

I expect the ACS to not have any unanswered proposal. I correct the state by accepting all pending proposals.

It is nuanced but I can see the distinction, thank you :+1:t2:

Now to make it happen :grinning:

1 Like

@cocreature noticed that in your second point

Any ACS event for the registered templates as well as completions for commands you sent from your trigger will fire the trigger

I understand that Any ACS event for the registered templates should fire the trigger, but I am not exactly sure why should the completion of commands sent from the trigger also fire the trigger. Do you mind to explain the reason behind it?

Btw always find your replies a savior here and there, thanks so much!

At a very high-level, we want to fire the trigger rule every time it would result in a command being emitted. Just firing the rule as fast as we can (given CPU limits) would accomplish that but that’s clearly very wasteful. In most cases, if the rule didn’t send a command last time it fired, it’s not going to send a command next time.

So how can we optimize that? The trigger rule gets access to certain state variables and only those. So as long as those don’t change, the rule is going to do the same thing it did last time and we don’t need to fire it again. Specifically, it gets access to 4 things:

  1. The ACS
  2. The command in flights
  3. The user defined state
  4. The time

The user defined state only changes when the trigger already sees an event so we can ignore those for this discussion.

Time is a bit special since it changes without any specific events. You can use heartbeats to enforce firing at least at the given interval.

Which leaves us with the first two:

The ACS is relatively clear. The trigger sees a transaciton, it changes the ACS so the rule fires.

Commands in flight (which are also used implicitly for the pending set and the dedup commands) is a bit more interesting: If we get a failed completion, we know the command is no longer in flight. In that case, the rule fires so the command can be retried (e.g., imagine the failure was due to a race on a contract key).

We don’t actually fire for successful completions. The reason there is quite subtle: For a successful command, we get two events: The successful completion and the corresponding transaction. The completion can arrive first. If we removed the command from the commands in flight at that point, the trigger would retry the command because it is no longer in flight but the state of the ACS has not yet change. Instead, we wait until we get the transaction and can also update the ACS and drop the command from the commands in flight only at that point and then fire the rule.

3 Likes

Could you mention a few trips and tricks for that? I am thinking about utilizing key uniqueness or using user defined state.