Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduling events based on time instead of blocks #413

Open
SebastienGllmt opened this issue Aug 10, 2024 · 0 comments
Open

Scheduling events based on time instead of blocks #413

SebastienGllmt opened this issue Aug 10, 2024 · 0 comments

Comments

@SebastienGllmt
Copy link
Contributor

SebastienGllmt commented Aug 10, 2024

Currently, when calling createScheduledData, you need to pass a blockHeight: number of when to trigger the event. Of course, if you're using emulated blocks or a chain that has a deterministic block time, this works fine. However, not all networks have this and not all use-cases benefit from emulated blocks.

It is actually possible right now to do this even without emulated blocks. You can do this using the following trick:

  1. Schedule an event for next block
  2. Once the event from (1) triggers, check the timestamp in the header
  3. If the timestamp is less than your target, just schedule an event again

Of course this works, but it's ugly to do this from a user perspective. It feels like something that is an engine-level feature

There are two ways we could handle this at the engine level:

  1. Reschedule every block: just like mentioned above, we could reschedule the event every block on their behalf until the right time has come. All this requires is tracking in the database some new target timestamp
    1. Advantage: requires minimal code change
    2. Disadvantage: constantly recreating events like this messes with statistics and explorers for the game (ex: Unblock EVM RPC emulation #411) because it's constantly spamming transactions (although at least they all get deleted)
  2. Create a new event type: instead of using the scheduled data which requires a block, we should have some new event system that instead uses a timestamp (or make the block field optional in the existing system, along with a new timestamp field). Then, when it's time for an STF call, we check if there any events with a timestamp ready to use
    1. Advantage: we don't need to constantly re-create scheduled data. It can just be a single event that gets triggered when necessary
    2. Disadvantage: requires handling of this new block-less schedule data type

Note: it's not really possible to have timers trigger the STF without a block being made, as this functionally ends up being equivalent to the emulated block feature

Additional consideration: timezones

If you want to schedule events at a specific time instead of a block, it's reasonable you may want to schedule for a specific timezone (ex: midnight PST). However, this has two unfortunate problems:

  1. Timezones change: some areas have daylight savings so the time mapping changes during the year. Additionally, if some US states decide to abolish daylight savings, the mapping of Unix timestamp to midnight changes as well. In practice, as long as all nodes apply this change at the same time in the same way, it won't cause any issues.
  2. Tricky support: to solve the problem mentioned in (1), we need a library to maps timezones in a way that is not only smart enough to handle things like daylight savings, but to also keep that library up-to-date to take into account any laws pass that affect timezones of importance

Implementing timezones: Date api

One option for implementing this is using nodejs's built-in support for the IANA timezone database. Although this is great as it means no external dependencies are required, it does come with some issues:

  1. It means that to update your timezone database, you also need to update your nodejs version
  2. The nodejs version of the database is not guaranteed to match the version used by your browser

However, if we did us the nodejs route, the code to start a timer based on midnight Japan starting from the first block would look like the following unfortunately long code block.

const startBlockDate = new Date(blockHeader.timestamp);

const [hours, minutes, seconds] = startBlockDate
  .toLocaleString('en-US', {
    timeZone: 'Asia/Singapore', // insert the desired timezone here
    hour: 'numeric',
    hour12: false,
    minute: 'numeric',
    second: 'numeric',
  })
  .split(':')
  .map(Number);

// since we can't know when a day ends (it ends at a different time during leap seconds for example)
// we instead subtract time back to 00:00
const midnightDate = new Date(
  startBlockDate.getTime() - (hours * 60 * 60 + minutes * 60 + seconds) * 1000
);
midnightDate.setUTCMilliseconds(0); // we want to ignore ms, which are timezone independent anyway
// add a day to get the next day
// DANGER: this can be subtly wrong if "next day" on your machine is different than "next day" in the timezone you're interested in. To fix this, you would have to do another round of `toLocaleString` and adjust any offset
//         but I think at this point this code block has conveyed the built-in Date API is definitely not the way to go due to its complexity
midnightDate.setDate(midnightDate.getDate() + 1);

// this is our final timestamp we want!
midnightDate.getTime()

Implementing timezones: Temporal api

There is an upcoming Temporal API coming to browsers and nodejs. It's currently supported on zero plaforms, but it is at stage 3 and the API is nice

// you need this import until Temporal is standardized
import { Temporal } from 'temporal-polyfill';

const midnightTimestamp = Temporal.Instant
  .fromEpochMilliseconds(blockHeader.timestamp)
  .toZonedDateTimeISO('Asia/Singapore') // insert your timezone here
  .add({ days: 1 })
  .startOfDay()
  .epochMilliseconds

Implementing timezones: dayjs package

This is a popular data management library in JS. It's not built-in, but it gets us a similar API as what Temporal would give us

const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');

dayjs.extend(utc);
dayjs.extend(timezone);

let specificTimestamp = new Date();

const nextDay = dayjs(blockHeader.timestamp)
    .tz('Asia/Singapore') // insert your timezone here
    .add(1, 'day')
    .startOf('day')
    .valueOf();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant