LiquidScheduler Walkthrough: Using Scheduled and Recurring Tasks

EOSIO has deprecated deferred transactions. Learn how to schedule tasks in your dApp.

DAPP Network
The DAPP Network Blog
5 min readApr 14, 2020

--

One crucial capability of many web applications is scheduled and recurring tasks. Modern apps need to be able to run maintenance tasks, data updates, and expirations and deadlines of all kinds.

For a decentralized application, it’s important to be able to execute these scheduled tasks in a trustless way.

Without a method for doing so, apps must rely on a centralized scheduling service, which is a vulnerable point. Or, they must rely on workarounds that may not apply well to each situation. One such workaround is, whenever a user of the dApp performs a certain action, checking for whether the deadline for a scheduled task has passed and performing that scheduled task if it has.

Since its launch, EOSIO had a “deferred transaction” feature which could schedule transactions in the future. This seemed useful, but was unreliable, as future transactions were never guaranteed. Actions which relied on deferred transactions had to have manual triggers put in place that could be activated if the deferred transaction failed.

Recent versions of EOSIO have deprecated deferred transactions. Developers must now look for other solutions.

LiquidScheduler is a fully-featured solution for scheduled and recurring tasks, with the same powerful characteristics of other DAPP Network services, such as customizable trustlessness.

LiquidScheduler Unboxed

The easiest way to get started, as usual, is by unboxing and investigating Zeus SDK’s sample box for the service: cron-dapp-service. Let’s check out the cronconsumer.cpp sample contract it gives us:

Preparing a Contract for LiquidScheduler

As usual, we must include some preliminaries to activate DAPP Network services, LiquidScheduler in particular, for this contract. Some of these will be specific to LiquidScheduler, and differ from preprocessor code for other services:

The internal name for LiquidScheduler — the name you’ll often see in the code — is CRON.

Again, as usual, we replace the usual class or CONTRACT line and the constructor that starts our contract class, as well as the usual ABI dispatcher at the end, with some macros:

…where testschedule represents all of the actions in the class. Other actions you may add will be listed here. ((testschedule)(rstschedule)), for example.

An Uncomplicated Schedule

The cronconsumer.cpp file is a simple one-file contract. Let’s take a look at its only action, testschedule:

(I prefer the ACTION macro, but the unboxed contract may have [[eosio::action]] void instead.)

This schedule_timer function we’ve called will trigger every 2 seconds, and will pass the payload through to a private function named timer_callback.

Passing In Some Data

The testschedule function as it stands doesn’t take any arguments, but we could easily require some data be submitted to the action. The second parameter of the schedule_timer function is a vector<char> type.

We can make this a little more versatile by packing a whole struct into our payload vector:

In this case, of course, we’d have to define our cron_struct, probably in our header file. In this way we could conveniently send in a bunch of data, and then put it back into struct form with something like:

These are good things to know, but let’s return to our original cronconsumer.cpp contract, which doesn’t pass any payload through. After all, some scheduled tasks will not need any special data.

The Callback Function

So all we have for testschedule is:

As it stands, our timer_callback function should be triggered here every 2 seconds by our DAPP Service Providers. Here’s what a timer_callback might look like. This is much simpler than the default in cronconsumer.cpp, but it’s the bare minimum we need.

Including this in our contract will result in it — and whatever code we add inside — being executed after the delay in seconds we passed in to schedule_timer.

Rescheduling

Note that schedule_timer only schedules one action. Whether or not more actions are scheduled after that depends on what our timer_callback function returns. Returning false will not reschedule the LiquidScheduler job. Returning true should continue rescheduling it indefinitely.

What if we only want it to run a certain number of times? Perhaps we are continually retrying a task, or the user only gets so many game powerup actions scheduled until they have to take some manual action to reset the maximum.

Let’s say we want to run this thing ten times. Well, we’re going to have to set up a singleton in our contract to keep track, and that’s exactly what cronconsumer.cpp does:

Normal EOSIO stuff here. A singleton is set up much like a multi_index table, but is a simpler option if you’re not dealing with multiple rows of data. If you’re unfamiliar with what we’re doing here, you might be in the wrong place. Get the basics from the EOSIO developer docs.

With our singleton ready to go, we can add some logic to our budding young timer_callback function:

There you have it! Now, whatever task we’re executing will run 10 times. We can test this with $ zeus test -c.

Of course, some other action in our contract may be able to update our TABLE stat in _self scope as well — for instance, if the user logs in to our game and so resets his maximum automatic energy boosts — so that could change the actual number of times.

Deploying Our Contract

The process of staking to the LiquidScheduler service offered by some DAPP Service Providers and deploying the contract is the same as our VRAM Kylin/mainnet deployment tutorial.

If you’re running on an EOSIO chain using LiquidX, such as WAX or Telos, some extra deployment steps are involved to link accounts across EOSIO chains. Those steps are described here.

For additional exploration of an app using LiquidScheduler — along with vRAM, LiquidOracles, and LiquidAccounts — check out the code for the sample LiquidPortfolio dApp. You can also view the LiquidScheduler getting started page.

--

--

DAPP Network
The DAPP Network Blog

DAPP Network aims to optimize development on the blockchain by equipping developers with a range of products for building and scaling dApps.