diff --git a/explorer/lib/explorer/contract_managers/aligned_layer_service_manager.ex b/explorer/lib/explorer/contract_managers/aligned_layer_service_manager.ex index fb31b19d50..bc276bba4b 100644 --- a/explorer/lib/explorer/contract_managers/aligned_layer_service_manager.ex +++ b/explorer/lib/explorer/contract_managers/aligned_layer_service_manager.ex @@ -136,7 +136,8 @@ defmodule AlignedLayerServiceManager do proof_hashes: nil, fee_per_proof: BatcherPaymentServiceManager.get_fee_per_proof(%{merkle_root: created_batch.batchMerkleRoot}), sender_address: Utils.string_to_bytes32(created_batch.senderAddress), - max_aggregator_fee: created_batch.maxAggregatorFee + max_aggregator_fee: created_batch.maxAggregatorFee, + is_valid: true # set to false later if a process determines it is invalid } end @@ -166,7 +167,8 @@ defmodule AlignedLayerServiceManager do fee_per_proof: unverified_batch.fee_per_proof, proof_hashes: nil, sender_address: unverified_batch.sender_address, - max_aggregator_fee: unverified_batch.max_aggregator_fee + max_aggregator_fee: unverified_batch.max_aggregator_fee, + is_valid: true # set to false later if a process determines it is invalid } end end diff --git a/explorer/lib/explorer/models/batch_structs.ex b/explorer/lib/explorer/models/batch_structs.ex index 0ba1329d1a..599c0f0d13 100644 --- a/explorer/lib/explorer/models/batch_structs.ex +++ b/explorer/lib/explorer/models/batch_structs.ex @@ -32,7 +32,8 @@ defmodule BatchDB do :proof_hashes, :fee_per_proof, :sender_address, - :max_aggregator_fee + :max_aggregator_fee, + :is_valid ] defstruct [ :merkle_root, @@ -48,6 +49,7 @@ defmodule BatchDB do :proof_hashes, :fee_per_proof, :sender_address, - :max_aggregator_fee + :max_aggregator_fee, + :is_valid ] end diff --git a/explorer/lib/explorer/models/batches.ex b/explorer/lib/explorer/models/batches.ex index 7d2b430afa..f2474ef485 100644 --- a/explorer/lib/explorer/models/batches.ex +++ b/explorer/lib/explorer/models/batches.ex @@ -18,6 +18,7 @@ defmodule Batches do field :fee_per_proof, :integer field :sender_address, :binary field :max_aggregator_fee, :decimal + field :is_valid, :boolean, default: true timestamps() end @@ -25,8 +26,8 @@ defmodule Batches do @doc false def changeset(new_batch, updates) do new_batch - |> cast(updates, [:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash, :submission_timestamp, :response_block_number, :response_transaction_hash, :response_timestamp, :data_pointer, :fee_per_proof, :sender_address, :max_aggregator_fee]) - |> validate_required([:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash, :fee_per_proof, :sender_address]) + |> cast(updates, [:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash, :submission_timestamp, :response_block_number, :response_transaction_hash, :response_timestamp, :data_pointer, :fee_per_proof, :sender_address, :max_aggregator_fee, :is_valid]) + |> validate_required([:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash, :fee_per_proof, :sender_address, :is_valid]) |> validate_format(:merkle_root, ~r/0x[a-fA-F0-9]{64}/) |> unique_constraint(:merkle_root) |> validate_number(:amount_of_proofs, greater_than: 0) @@ -37,6 +38,7 @@ defmodule Batches do |> validate_format(:response_transaction_hash, ~r/0x[a-fA-F0-9]{64}/) |> validate_number(:max_aggregator_fee, greater_than: 0) |> validate_number(:fee_per_proof, greater_than_or_equal_to: 0) + |> validate_inclusion(:is_valid, [true, false]) end def cast_to_batches(%BatchDB{} = batch_db) do @@ -53,7 +55,8 @@ defmodule Batches do data_pointer: batch_db.data_pointer, fee_per_proof: batch_db.fee_per_proof, sender_address: batch_db.sender_address, - max_aggregator_fee: batch_db.max_aggregator_fee + max_aggregator_fee: batch_db.max_aggregator_fee, + is_valid: batch_db.is_valid } end @@ -113,7 +116,7 @@ defmodule Batches do threshold_datetime = DateTime.utc_now() |> DateTime.add(-43200, :second) # 12 hours ago query = from(b in Batches, - where: b.is_verified == false and b.submission_timestamp > ^threshold_datetime, + where: b.is_valid == true and b.is_verified == false and b.submission_timestamp > ^threshold_datetime, select: b) Explorer.Repo.all(query) @@ -193,5 +196,4 @@ defmodule Batches do end end end - end diff --git a/explorer/lib/explorer/periodically.ex b/explorer/lib/explorer/periodically.ex index 423b3738e6..403d651403 100644 --- a/explorer/lib/explorer/periodically.ex +++ b/explorer/lib/explorer/periodically.ex @@ -45,6 +45,7 @@ defmodule Explorer.Periodically do run_every_n_iterations = 8 new_count = rem(count + 1, run_every_n_iterations) + if new_count == 0 do Task.start(&process_unverified_batches/0) end @@ -79,25 +80,19 @@ defmodule Explorer.Periodically do {:ok, lock} -> "Processing batch: #{batch.merkle_root}" |> Logger.debug() - {batch_changeset, proofs} = - batch - |> Utils.extract_info_from_data_pointer() - |> Batches.generate_changesets() - - Batches.insert_or_update(batch_changeset, proofs) - |> case do - {:ok, _} -> - PubSub.broadcast(Explorer.PubSub, "update_views", %{ - eth_usd: - case EthConverter.get_eth_price_usd() do - {:ok, eth_usd_price} -> eth_usd_price - {:error, _error} -> :empty - end - }) - - {:error, error} -> - Logger.error("Some error in DB operation, not broadcasting update_views: #{inspect(error)}") - + with {:ok, updated_batch} <- Utils.process_batch(batch), + {batch_changeset, proofs} <- Batches.generate_changesets(updated_batch), + {:ok, _} <- Batches.insert_or_update(batch_changeset, proofs) do + PubSub.broadcast(Explorer.PubSub, "update_views", %{ + eth_usd: + case EthConverter.get_eth_price_usd() do + {:ok, eth_usd_price} -> eth_usd_price + {:error, _error} -> :empty + end + }) + else + {:error, reason} -> + Logger.error("Error processing batch #{batch.merkle_root}. Error: #{inspect(reason)}") # no changes in DB nil -> nil diff --git a/explorer/lib/explorer_web/components/core_components.ex b/explorer/lib/explorer_web/components/core_components.ex index 54976b84f2..02a49e5653 100644 --- a/explorer/lib/explorer_web/components/core_components.ex +++ b/explorer/lib/explorer_web/components/core_components.ex @@ -417,15 +417,15 @@ defmodule ExplorerWeb.CoreComponents do end @doc """ - Renders a dynamic badge compoent. + Renders a dynamic badge component. """ attr :class, :string, default: nil attr :status, :boolean, default: true - attr :falsy_text, :string, default: "Pending" - attr :truthy_text, :string, default: "Verified" + attr :falsy_text, :string + attr :truthy_text, :string slot :inner_block, default: nil - def dynamic_badge(assigns) do + def dynamic_badge_boolean(assigns) do ~H""" <.badge variant={ @@ -449,6 +449,39 @@ defmodule ExplorerWeb.CoreComponents do """ end + @doc """ + Renders a dynamic badge component for the batcher. + """ + attr :class, :string, default: nil + attr :status, :atom + slot :inner_block, default: nil + + def dynamic_badge_for_batcher(assigns) do + ~H""" + <.badge + variant={ + case @status do + :invalid -> "destructive" + :verified -> "accent" + :pending -> "foreground" + end + } + class={ + classes([ + @class + ]) + } + > + <%= case @status do + :invalid -> "Invalid" + :verified -> "Verified" + :pending -> "Pending" + end %> + <%= render_slot(@inner_block) %> + + """ + end + @doc """ Renders an input with label and error messages. diff --git a/explorer/lib/explorer_web/live/pages/batch/index.html.heex b/explorer/lib/explorer_web/live/pages/batch/index.html.heex index 98636fec5e..764f12219c 100644 --- a/explorer/lib/explorer_web/live/pages/batch/index.html.heex +++ b/explorer/lib/explorer_web/live/pages/batch/index.html.heex @@ -25,7 +25,7 @@

Status:

- <.dynamic_badge class="w-fit" status={@current_batch.is_verified} /> + <.dynamic_badge_for_batcher class="w-fit" status={Helpers.get_batch_status(@current_batch)} />

diff --git a/explorer/lib/explorer_web/live/pages/batches/index.html.heex b/explorer/lib/explorer_web/live/pages/batches/index.html.heex index 044e43a14e..26ce5ac2ee 100644 --- a/explorer/lib/explorer_web/live/pages/batches/index.html.heex +++ b/explorer/lib/explorer_web/live/pages/batches/index.html.heex @@ -14,7 +14,7 @@ <:col :let={batch} label="Status"> - <.dynamic_badge status={batch.is_verified} /> + <.dynamic_badge_for_batcher status={Helpers.get_batch_status(batch)} /> <:col :let={batch} label="Age"> diff --git a/explorer/lib/explorer_web/live/pages/operators/index.ex b/explorer/lib/explorer_web/live/pages/operators/index.ex index 563cfce078..dab94a92e8 100644 --- a/explorer/lib/explorer_web/live/pages/operators/index.ex +++ b/explorer/lib/explorer_web/live/pages/operators/index.ex @@ -83,7 +83,7 @@ defmodule ExplorerWeb.Operators.Index do <%= operator.total_stake |> EthConverter.wei_to_eth(2) |> Helpers.format_number() %> ETH <:col :let={operator} label="Status"> - <.dynamic_badge status={operator.is_active} truthy_text="Active" falsy_text="Inactive" /> + <.dynamic_badge_boolean status={operator.is_active} truthy_text="Active" falsy_text="Inactive" /> <% else %> diff --git a/explorer/lib/explorer_web/live/utils.ex b/explorer/lib/explorer_web/live/utils.ex index 048e363819..270083825d 100644 --- a/explorer/lib/explorer_web/live/utils.ex +++ b/explorer/lib/explorer_web/live/utils.ex @@ -127,6 +127,14 @@ defmodule ExplorerWeb.Helpers do def binary_to_hex_string(binary) do Utils.binary_to_hex_string(binary) end + + def get_batch_status(batch) do + cond do + not batch.is_valid -> :invalid + batch.is_verified -> :verified + true -> :pending + end + end end # Backend utils @@ -199,7 +207,7 @@ defmodule Utils do defp check_batch_size(size, acc) do if size > @max_batch_size do - {:halt, {:error, {:http, :body_too_large}}} + {:halt, {:error, {:invalid, :body_too_large}}} else {:cont, acc} end @@ -233,7 +241,7 @@ defmodule Utils do true -> Logger.error("Unknown S3 object format") - {:error, :unknown_format} + {:error, {:invalid, :unknown_format}} end {:error, reason} -> @@ -264,36 +272,58 @@ defmodule Utils do end end - def extract_info_from_data_pointer(%BatchDB{} = batch) do + def process_batch(%BatchDB{} = batch) do + case get_proof_hashes(batch) do + {:ok, proof_hashes} -> + {:ok, add_proof_hashes_to_batch(batch, proof_hashes)} + + {:error, {:invalid, reason}} -> + Logger.error("Invalid batch content for #{batch.merkle_root}: #{inspect(reason)}") + # Returning something ensures we avoid attempting to fetch the invalid data again. + updated_batch = + batch + |> Map.put(:is_valid, false) + |> add_proof_hashes_to_batch([<<0>>]) + + {:ok, updated_batch} + + {:error, reason} -> + {:error, reason} + end + end + + defp add_proof_hashes_to_batch(batch, proof_hashes) do + batch + |> Map.put(:proof_hashes, proof_hashes) + |> Map.put(:amount_of_proofs, Enum.count(proof_hashes)) + end + + defp get_proof_hashes(%BatchDB{} = batch) do Logger.debug("Extracting batch's proofs info: #{batch.merkle_root}") # only get from s3 if not already in DB - proof_hashes = - case Proofs.get_proofs_from_batch(%{merkle_root: batch.merkle_root}) do - nil -> - Logger.debug("Fetching from S3") + case Proofs.get_proofs_from_batch(%{merkle_root: batch.merkle_root}) do + nil -> + Logger.debug("Fetching from S3") - batch_content = batch.data_pointer |> Utils.fetch_batch_data_pointer() + batch_content = batch.data_pointer |> Utils.fetch_batch_data_pointer() - case batch_content do - {:ok, batch_content} -> + case batch_content do + {:ok, batch_content} -> + proof_hashes = batch_content |> Utils.calculate_proof_hashes() - {:error, reason} -> - Logger.error("Error fetching batch content: #{inspect(reason)}") - # Returning something ensures we avoid attempting to fetch the invalid data again. - [<<0>>] - end + {:ok, proof_hashes} - proof_hashes -> - # already processed and stored the S3 data - Logger.debug("Fetching from DB") - proof_hashes - end + {:error, reason} -> + {:error, reason} + end - batch - |> Map.put(:proof_hashes, proof_hashes) - |> Map.put(:amount_of_proofs, proof_hashes |> Enum.count()) + proof_hashes -> + # already processed and stored the S3 data + Logger.debug("Fetching from DB") + {:ok, proof_hashes} + end end def fetch_eigen_operator_metadata(url) do diff --git a/explorer/priv/repo/migrations/20241010133259_add_is_valid_field.exs b/explorer/priv/repo/migrations/20241010133259_add_is_valid_field.exs new file mode 100644 index 0000000000..0864313352 --- /dev/null +++ b/explorer/priv/repo/migrations/20241010133259_add_is_valid_field.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddIsValidField do + use Ecto.Migration + + def change do + alter table("batches") do + add :is_valid, :boolean, default: true + end + end +end