yral-dapp / hot-or-not-backend-canister

Other
9 stars 6 forks source link

Second PR - HON game (infinite timers) #354

Closed abhishek-tripathi-yral closed 3 weeks ago

abhishek-tripathi-yral commented 3 months ago

Merge this only after #352

abhishek-tripathi-yral commented 3 months ago

Rebased on first PR . Please merge this only after merging first one.

abhishek-tripathi-yral commented 3 months ago

on branch abhi-yral-005 hotornot_game_simulation_test_after_yral_game_3 has been put as #[ignore] for the branch. Feel free to run that locally. This is a counterpart test to hotornot_game_simulation_test_2

abhishek-tripathi-yral commented 3 months ago

General pointers I think that might be useful for PR review.

  1. if any new integration tests are needed.
  2. backups are being done correctly or not? I request to review that part since I am unsure about the backup logic
abhishek-tripathi-yral commented 3 months ago

more debugging related exploration here: https://gist.github.com/abeeshake-yral/ae98a6bf2028d7d2c6687a8684255b06

abhishek-tripathi-yral commented 3 months ago

Betting starts when the first user bets on a given post. Betting slot is alloted thena and a timer for 1 hr is started.

Here are two apporaches to implement the timer:

  1. have a timer for each post which is being betted on
  2. have a global timer and a queue of posts that are being betted on.
    1. iterate over the queue and check if the timer has expired.
    2. if the timer has expired, then tabulate the outcome for the post and remove the post from the queue.

We chose the second approach because it is more efficient.

Here are the scenarios that can happen for the timers:

post_2_Z => means post 2 for the user with id Z

  1. User A bets on a post_1_Z at 04:00 and the global timer for 1 hr is started (to be finished at 05:00)
  2. User B bets on a post_1_Z at 04:01 and no timer is started. (since there should be ONLY one timer in the canister at a time). It is inserted into a queue. Since this bet lies in the same slot as step 1. => it is evaluated when timer in step 1. expires.
  3. User C bets on a post_2_Z at 04:02 and no timer is started. (since there should be ONLY one timer in the canister at a time). It is inserted into a queue. This is a bet on a different post (post_id = 2).

When timer in step 1. expires, result for post_1_Z is evaluated via tabulate_hot_or_not_outcome_for_post_slot_v1 and post_1_Z is removed from the queue. Note that there were two bets on post_1_Z (from User A and User B).

Next timer is queued for the next post (post_2_Z) in queue. This second timer actually needs to run only for 1 minute since this timer is started at 05:00 (after first timer from step 1. finished) and the expiry of this timer needs to be at 05:01. Computationally, the function tabulate_hot_or_not_outcome_for_post_slot_v1 after 1 hr for post_2_Z.

Here are the technical details for implementing the timer:

  1. queue is implemented in stable memory as ic_stable_structures::btree_map::BTreeMap because it has pop_first and insert methods. The Vec in ic_stable_structures doesn't have pop_first.
  2. we also need to keep track of the time when the first bet was placed for each post. This is because timer expires 1 hr after the first bet on the given post is placed. (refer step 1. and 2 from previous section of this document)
    pub bet_timer_posts: ic_stable_structures::btreemap::BTreeMap<(SystemTimeInMs, PostId), (), Memory>,

Here are the scenarios that can happen for the timers:

A1. when first bet on post_1_Z is placed, and bet_timer_posts is empty. We insert into bet_timer_posts a tuple of (SystemTimeInMs, PostId) and start_timer.

A2. when second bet on post_1_Z is placed, we check if PostId(post_1_Z) exists in bet_timer_posts. if it doesn't, then we insert into bet_timer_posts a tuple of (SystemTimeInMs, PostId). we don't ned to call start_timer here. Since there must be a global timer running. It was set in A1.

A3. when first bet on post_2_Z is placed, we check if PostId(post_2_Z) exists in bet_timer_posts. It doesn't. So, we insert into bet_timer_posts a tuple of (SystemTimeInMs, PostId). we don't ned to call start_timer here.

A4. when timer for post_1_Z expires, we tabulate_hot_or_not_outcome_for_post_slot_v1 for post_1_Z and remove it from bet_timer_posts. also, start the next timer (via start_timer) for the next post (post_2_Z) in the queue bet_timer_posts.

Now, in A2 above, to check if the posts has already been betted upon, in O(1) time, we need a separate BTreeMap<PostId, ()>. we call this map first_bet_placed_at_hashmap. now this map needs to be updated always along with bet_timer_posts. at the cost of memory, we are optimising for lookup speed.

ravi-sawlani-yral commented 3 weeks ago

Closing this as this is no longer required.