From 0995b46313aa2a1406889354be04650344ff583a Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 21 Dec 2023 18:38:44 +0100 Subject: [PATCH] Utils.time_offset is more accurate --- lib/archethic/beacon_chain/slot_timer.ex | 6 +-- lib/archethic/beacon_chain/summary_timer.ex | 6 +-- lib/archethic/contracts/worker.ex | 5 +-- lib/archethic/networking/scheduler.ex | 2 +- lib/archethic/oracle_chain.ex | 1 - lib/archethic/oracle_chain/scheduler.ex | 21 +++------ .../oracle_chain/services/hydrating_cache.ex | 2 +- lib/archethic/reward/scheduler.ex | 2 +- lib/archethic/self_repair.ex | 2 - lib/archethic/self_repair/scheduler.ex | 7 +-- .../shared_secrets/node_renewal_scheduler.ex | 11 +---- lib/archethic/utils.ex | 45 +++++++++---------- 12 files changed, 40 insertions(+), 70 deletions(-) diff --git a/lib/archethic/beacon_chain/slot_timer.ex b/lib/archethic/beacon_chain/slot_timer.ex index 2ef8a5c4e..fc79a4ea9 100644 --- a/lib/archethic/beacon_chain/slot_timer.ex +++ b/lib/archethic/beacon_chain/slot_timer.ex @@ -33,9 +33,7 @@ defmodule Archethic.BeaconChain.SlotTimer do """ @spec next_slot(DateTime.t()) :: DateTime.t() def next_slot(date_from = %DateTime{}) do - get_interval() - |> CronParser.parse!(true) - |> Utils.next_date(date_from) + get_interval() |> Utils.next_date(date_from) end @doc """ @@ -162,7 +160,7 @@ defmodule Archethic.BeaconChain.SlotTimer do end defp schedule_new_slot(interval) do - Process.send_after(self(), :new_slot, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :new_slot, Utils.time_offset(interval)) end def config_change(nil), do: :ok diff --git a/lib/archethic/beacon_chain/summary_timer.ex b/lib/archethic/beacon_chain/summary_timer.ex index b0b8e99bf..fc278e09d 100644 --- a/lib/archethic/beacon_chain/summary_timer.ex +++ b/lib/archethic/beacon_chain/summary_timer.ex @@ -25,9 +25,7 @@ defmodule Archethic.BeaconChain.SummaryTimer do """ @spec next_summary(DateTime.t()) :: DateTime.t() def next_summary(date_from = %DateTime{}) do - get_interval() - |> CronParser.parse!(true) - |> Utils.next_date(date_from) + get_interval() |> Utils.next_date(date_from) end @doc """ @@ -125,7 +123,7 @@ defmodule Archethic.BeaconChain.SummaryTimer do end defp schedule_next_summary_time(interval) do - Process.send_after(self(), :next_summary_time, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :next_summary_time, Utils.time_offset(interval)) end def config_change(nil), do: :ok diff --git a/lib/archethic/contracts/worker.ex b/lib/archethic/contracts/worker.ex index 232207e6e..dbc025f4b 100644 --- a/lib/archethic/contracts/worker.ex +++ b/lib/archethic/contracts/worker.ex @@ -17,7 +17,6 @@ defmodule Archethic.Contracts.Worker do alias Archethic.TransactionChain.TransactionData.Recipient alias Archethic.Utils alias Archethic.Utils.DetectNodeResponsiveness - alias Crontab.CronExpression.Parser, as: CronParser @extended_mode? Mix.env() != :prod @@ -194,7 +193,7 @@ defmodule Archethic.Contracts.Worker do defp schedule_trigger(trigger = {:interval, interval}, triggers_type) do now = DateTime.utc_now() - next_tick = interval |> CronParser.parse!(@extended_mode?) |> Utils.next_date(now) + next_tick = Utils.next_date(interval, now, @extended_mode?) # do not allow an interval trigger if there is a datetime trigger at same time # because one of them would get a "transaction is already mining" @@ -204,7 +203,7 @@ defmodule Archethic.Contracts.Worker do "Contract scheduler skips next tick for trigger=interval because there is a trigger=datetime at the same time that takes precedence" ) - interval |> CronParser.parse!(@extended_mode?) |> Utils.next_date(next_tick) + Utils.next_date(interval, next_tick, @extended_mode?) else next_tick end diff --git a/lib/archethic/networking/scheduler.ex b/lib/archethic/networking/scheduler.ex index b6117611c..d84d0b16d 100644 --- a/lib/archethic/networking/scheduler.ex +++ b/lib/archethic/networking/scheduler.ex @@ -89,7 +89,7 @@ defmodule Archethic.Networking.Scheduler do end defp schedule_update(interval) do - Process.send_after(self(), :update, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :update, Utils.time_offset(interval)) end defp do_update do diff --git a/lib/archethic/oracle_chain.ex b/lib/archethic/oracle_chain.ex index 76a03bf8c..086a39498 100644 --- a/lib/archethic/oracle_chain.ex +++ b/lib/archethic/oracle_chain.ex @@ -148,7 +148,6 @@ defmodule Archethic.OracleChain do def next_summary_date(date_from = %DateTime{}) do Application.get_env(:archethic, Scheduler) |> Keyword.fetch!(:summary_interval) - |> CronParser.parse!(true) |> Utils.next_date(date_from) end diff --git a/lib/archethic/oracle_chain/scheduler.ex b/lib/archethic/oracle_chain/scheduler.ex index 33c9452cf..135b26cf0 100644 --- a/lib/archethic/oracle_chain/scheduler.ex +++ b/lib/archethic/oracle_chain/scheduler.ex @@ -8,8 +8,6 @@ defmodule Archethic.OracleChain.Scheduler do alias OracleChain.{Services, Summary} alias TransactionChain.{Transaction, TransactionData} - alias Crontab.CronExpression.Parser, as: CronParser - use GenStateMachine, callback_mode: [:handle_event_function] @vsn Mix.Project.config()[:version] @@ -69,8 +67,7 @@ defmodule Archethic.OracleChain.Scheduler do # This case may happen in case of process restart after crash {:ok, %Node{authorized?: true, available?: true}} -> summary_date = - Map.get(state_data, :summary_interval) - |> next_date() + Map.get(state_data, :summary_interval) |> Utils.next_date(DateTime.utc_now()) PubSub.register_to_new_transaction_by_type(:oracle) PubSub.register_to_new_transaction_by_type(:oracle_summary) @@ -102,7 +99,7 @@ defmodule Archethic.OracleChain.Scheduler do _state, data = %{polling_interval: polling_interval, indexes: indexes, summary_date: summary_date} ) do - polling_date = next_date(polling_interval) + polling_date = Utils.next_date(polling_interval, DateTime.utc_now()) polling_timer = case Map.get(data, :polling_timer) do @@ -419,7 +416,7 @@ defmodule Archethic.OracleChain.Scheduler do data = %{summary_interval: summary_interval} ) do # Discard the oracle summary if there is not previous indexing - next_summary_date = next_date(summary_interval) + next_summary_date = Utils.next_date(summary_interval, DateTime.utc_now()) Logger.info("Next Oracle Summary at #{DateTime.to_string(next_summary_date)}") new_data = @@ -469,7 +466,7 @@ defmodule Archethic.OracleChain.Scheduler do data = %{summary_interval: summary_interval, polling_interval: polling_interval} ) do if Crypto.first_node_public_key() == first_public_key do - next_summary_date = next_date(summary_interval) + next_summary_date = Utils.next_date(summary_interval, DateTime.utc_now()) index = chain_size(next_summary_date) other_authorized_nodes = @@ -482,7 +479,7 @@ defmodule Archethic.OracleChain.Scheduler do case other_authorized_nodes do [] -> - next_polling_date = next_date(polling_interval) + next_polling_date = Utils.next_date(polling_interval, DateTime.utc_now()) new_data = data @@ -591,7 +588,7 @@ defmodule Archethic.OracleChain.Scheduler do defp update_summary_date(data = %{summary_interval: summary_interval}) do OracleChain.update_summ_gen_addr() - next_summary_date = next_date(summary_interval) + next_summary_date = Utils.next_date(summary_interval, DateTime.utc_now()) Logger.info("Next Oracle Summary at #{DateTime.to_string(next_summary_date)}") data @@ -738,12 +735,6 @@ defmodule Archethic.OracleChain.Scheduler do end end - defp next_date(interval) do - interval - |> CronParser.parse!(true) - |> Utils.next_date(DateTime.utc_now()) - end - defp get_new_oracle_data(summary_date, index) do summary_date |> Crypto.derive_oracle_address(index) diff --git a/lib/archethic/oracle_chain/services/hydrating_cache.ex b/lib/archethic/oracle_chain/services/hydrating_cache.ex index abbc799f6..c01d9d545 100644 --- a/lib/archethic/oracle_chain/services/hydrating_cache.ex +++ b/lib/archethic/oracle_chain/services/hydrating_cache.ex @@ -154,7 +154,7 @@ defmodule Archethic.OracleChain.Services.HydratingCache do defp next_tick_in_seconds(refresh_interval) do if is_binary(refresh_interval) do - Utils.time_offset(refresh_interval) * 1000 + Utils.time_offset(refresh_interval) else refresh_interval end diff --git a/lib/archethic/reward/scheduler.ex b/lib/archethic/reward/scheduler.ex index a3a149516..5a6be7f84 100644 --- a/lib/archethic/reward/scheduler.ex +++ b/lib/archethic/reward/scheduler.ex @@ -409,7 +409,7 @@ defmodule Archethic.Reward.Scheduler do end defp schedule(interval) do - Process.send_after(self(), :mint_rewards, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :mint_rewards, Utils.time_offset(interval)) end defp trigger_node?(validation_nodes, count \\ 0) do diff --git a/lib/archethic/self_repair.ex b/lib/archethic/self_repair.ex index 0f817af19..578ff2216 100755 --- a/lib/archethic/self_repair.ex +++ b/lib/archethic/self_repair.ex @@ -117,14 +117,12 @@ defmodule Archethic.SelfRepair do :archethic |> Application.get_env(SummaryTimer, []) |> Keyword.fetch!(:interval) - |> CronParser.parse!(true) |> Utils.next_date(last_sync_date) next_repair_date = :archethic |> Application.get_env(Scheduler, []) |> Keyword.fetch!(:interval) - |> CronParser.parse!(true) |> Utils.next_date(next_summary_date) DateTime.compare(DateTime.utc_now(), next_repair_date) != :lt diff --git a/lib/archethic/self_repair/scheduler.ex b/lib/archethic/self_repair/scheduler.ex index 86987d546..4b63d5ee8 100644 --- a/lib/archethic/self_repair/scheduler.ex +++ b/lib/archethic/self_repair/scheduler.ex @@ -19,7 +19,6 @@ defmodule Archethic.SelfRepair.Scheduler do alias Archethic.Utils alias Archethic.Bootstrap.Sync, as: BootstrapSync - alias Crontab.CronExpression.Parser, as: CronParser require Logger @@ -169,7 +168,7 @@ defmodule Archethic.SelfRepair.Scheduler do end defp schedule_sync(interval) do - Process.send_after(self(), :sync, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :sync, Utils.time_offset(interval)) end defp last_sync_date_to_string(last_sync_date) do @@ -203,8 +202,6 @@ defmodule Archethic.SelfRepair.Scheduler do """ @spec next_repair_time(DateTime.t()) :: DateTime.t() def next_repair_time(ref_time \\ DateTime.utc_now()) do - get_interval() - |> CronParser.parse!(true) - |> Utils.next_date(ref_time) + get_interval() |> Utils.next_date(ref_time) end end diff --git a/lib/archethic/shared_secrets/node_renewal_scheduler.ex b/lib/archethic/shared_secrets/node_renewal_scheduler.ex index 43a9b8635..8393c225b 100644 --- a/lib/archethic/shared_secrets/node_renewal_scheduler.ex +++ b/lib/archethic/shared_secrets/node_renewal_scheduler.ex @@ -7,8 +7,6 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do them as new authorized nodes and update the daily nonce seed. """ - alias Crontab.CronExpression.Parser, as: CronParser - alias Archethic alias Archethic.Election @@ -320,7 +318,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do end defp schedule_renewal_message(interval) do - Process.send_after(self(), :make_renewal, Utils.time_offset(interval) * 1000) + Process.send_after(self(), :make_renewal, Utils.time_offset(interval)) end defp trigger_node?(validation_nodes, count \\ 0) do @@ -339,14 +337,9 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do """ @spec next_application_date(DateTime.t()) :: DateTime.t() def next_application_date(date_from = %DateTime{}) do - get_application_date_interval() - |> CronParser.parse!(true) - |> Utils.next_date(date_from) - end - - defp get_application_date_interval do Application.get_env(:archethic, __MODULE__) |> Keyword.fetch!(:application_interval) + |> Utils.next_date(date_from) end def code_change(_old_vsn, state, data, _extra), do: {:ok, state, data} diff --git a/lib/archethic/utils.ex b/lib/archethic/utils.ex index 9193dcc12..8a09dc644 100644 --- a/lib/archethic/utils.ex +++ b/lib/archethic/utils.ex @@ -60,34 +60,28 @@ defmodule Archethic.Utils do ## Examples # Time offset for the next 2 seconds - iex> Utils.time_offset("*/2 * * * * *", ~U[2020-09-24 20:13:12.10Z]) - 2 - # 12 seconds + offset == 14 seconds + iex> Utils.time_offset("*/2 * * * * *", ref_time: ~U[2020-09-24 20:13:12.10Z], time_unit: :millisecond) + 1900 # Time offset for the next minute - iex> Utils.time_offset("0 * * * * *", ~U[2020-09-24 20:13:12.00Z]) - 48 - # 12 seconds + offset == 60 seconds (1 minute) + iex> Utils.time_offset("0 * * * * *", ref_time: ~U[2020-09-24 20:13:12.00Z]) + 48000 # Time offset for the next hour - iex> Utils.time_offset("0 0 * * * *", ~U[2020-09-24 20:13:00Z]) + iex> Utils.time_offset("0 0 * * * *", ref_time: ~U[2020-09-24 20:13:00Z], time_unit: :second) 2820 - # 13 minutes: 720 seconds + offset == 3600 seconds (one hour) # Time offset for the next day - iex> Utils.time_offset("0 0 0 * * *", ~U[2020-09-24 00:00:01Z]) + iex> Utils.time_offset("0 0 0 * * *", ref_time: ~U[2020-09-24 00:00:01Z], time_unit: :second) 86399 - # 1 second + offset = 86400 (1 day) """ - @spec time_offset(cron_interval :: binary()) :: seconds :: non_neg_integer() - def time_offset(interval, ref_time \\ DateTime.utc_now(), extended_mode \\ true) do - next_slot = - interval - |> CronParser.parse!(extended_mode) - |> CronScheduler.get_next_run_date!(DateTime.to_naive(ref_time)) - |> DateTime.from_naive!("Etc/UTC") + @spec time_offset(cron_interval :: binary(), opts :: Keyword.t()) :: offset :: non_neg_integer() + def time_offset(interval, opts \\ []) do + ref_time = Keyword.get(opts, :ref_time, DateTime.utc_now()) + extended_mode = Keyword.get(opts, :extended_mode, true) + time_unit = Keyword.get(opts, :time_unit, :millisecond) - DateTime.diff(next_slot, ref_time, :second) + interval |> next_date(ref_time, extended_mode) |> DateTime.diff(ref_time, time_unit) end @doc """ @@ -836,20 +830,23 @@ defmodule Archethic.Utils do ## Examples - iex> Utils.next_date(%Crontab.CronExpression{second: [{:/, :*, 10}], extended: true}, ~U[2022-10-01 01:00:00Z]) + iex> Utils.next_date("*/10 * * * * * *", ~U[2022-10-01 01:00:00Z]) ~U[2022-10-01 01:00:10Z] - iex> Utils.next_date(%Crontab.CronExpression{second: [{:/, :*, 10}], extended: true}, ~U[2022-10-01 01:00:00.100Z]) + iex> Utils.next_date("*/10 * * * * * *", ~U[2022-10-01 01:00:00.100Z]) ~U[2022-10-01 01:00:10Z] - iex> Utils.next_date(%Crontab.CronExpression{second: [{:/, :*, 10}], extended: true}, ~U[2022-10-01 01:00:05Z]) + iex> Utils.next_date("*/10 * * * * * *", ~U[2022-10-01 01:00:05Z]) ~U[2022-10-01 01:00:10Z] - iex> Utils.next_date(%Crontab.CronExpression{second: [{:/, :*, 10}], extended: true}, ~U[2022-10-01 01:00:05.139Z]) + iex> Utils.next_date("*/10 * * * * * *", ~U[2022-10-01 01:00:05.139Z]) ~U[2022-10-01 01:00:10Z] """ - @spec next_date(Crontab.CronExpression.t(), DateTime.t()) :: DateTime.t() - def next_date(cron_expression, date_from = %DateTime{}) do + @spec next_date(interval :: binary(), date_from :: DateTime.t(), extended_mode? :: boolean()) :: + next_date :: DateTime.t() + def next_date(interval, date_from = %DateTime{}, extended_mode? \\ true) do + cron_expression = CronParser.parse!(interval, extended_mode?) + naive_date_from = DateTime.to_naive(date_from) if Crontab.DateChecker.matches_date?(cron_expression, naive_date_from) do