-
Notifications
You must be signed in to change notification settings - Fork 396
feat: aggregation mode explorer #1846
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
Changes from 71 commits
72d0478
7a95c59
0a46e9c
4aa0e46
a0aa4af
7065367
6f2b090
38eea41
993333d
69db30c
943b83d
051c882
6ffb456
c674b3b
bb7f7d2
554abb1
4fa59f4
8885b02
bcab0d1
4645f14
7ff2d4c
0217c99
7d4e14b
ff12d58
99a6a37
f9a8acc
f7ed7b9
cef6e1a
6fc017d
b5c7aa3
86a2815
1ab8c7e
1be8530
93fcccd
1935850
cd20c34
428ac5d
407808b
9f94d11
db5a2b0
2a4791d
18be091
34a0c55
7efb80e
28757e0
8ba0339
3406e43
1b22fd7
36a9a93
7f500c6
2a11aa0
0f07b05
e3883d0
2e11a4b
8dc3273
22a493f
95337bf
39e4018
ca56e00
0e71dd5
d5741ed
f95139f
6e0c26b
7ad8641
6831ab4
7db5f31
2712e9e
54281dd
97019e9
cb06d2f
c4f2633
48be71c
981828d
609d744
3d66f25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| defmodule Explorer.BeaconClient do | ||
| require Logger | ||
| @beacon_url System.get_env("BEACON_CLIENT") | ||
| # See https://eips.ethereum.org/EIPS/eip-4844#parameters | ||
| @versioned_hash_version_kzg 0x01 | ||
|
|
||
| def fetch_blob_by_versioned_hash!(slot, blob_versioned_hash) do | ||
| {:ok, blobs} = get_block_blobs(slot) | ||
| data = Map.get(blobs, "data") | ||
|
|
||
| Enum.find(data, fn blob -> | ||
| get_blob_versioned_hash(blob) == blob_versioned_hash | ||
| end) | ||
| end | ||
|
|
||
| def get_blob_versioned_hash(blob) do | ||
| kzg_commitment = String.replace(Map.get(blob, "kzg_commitment"), "0x", "") | ||
| kzg_commitment = Base.decode16!(kzg_commitment, case: :mixed) | ||
| hash = Explorer.Utils.sha256_hash_raw(kzg_commitment) | ||
| # See https://eips.ethereum.org/EIPS/eip-4844#helpers | ||
| <<_first::8, rest::binary>> = hash | ||
| raw = <<@versioned_hash_version_kzg::8>> <> rest | ||
| "0x" <> Base.encode16(raw, case: :lower) | ||
| end | ||
|
|
||
| def get_block_slot(beacon_block) do | ||
| String.to_integer( | ||
| beacon_block | ||
| |> Map.get("data") | ||
| |> Map.get("header") | ||
| |> Map.get("message") | ||
| |> Map.get("slot") | ||
| ) | ||
| end | ||
|
|
||
| def get_block_header_by_hash(block_hash) do | ||
| beacon_get("/eth/v1/beacon/headers/#{block_hash}") | ||
| end | ||
|
|
||
| def get_block_header_by_parent_hash(parent_block_hash) do | ||
| case beacon_get("/eth/v1/beacon/headers?parent_root=#{parent_block_hash}") do | ||
| {:ok, header} -> | ||
| data = header["data"] |> Enum.at(0) | ||
|
|
||
| {:ok, %{header | "data" => data}} | ||
|
|
||
| other -> | ||
| other | ||
| end | ||
| end | ||
|
|
||
| def get_block_blobs(slot) do | ||
| beacon_get("/eth/v1/beacon/blob_sidecars/#{slot}") | ||
| end | ||
|
|
||
| def beacon_get(method) do | ||
| headers = [{"Content-Type", "application/json"}] | ||
| request = Finch.build(:get, "#{@beacon_url}#{method}", headers) | ||
| response = Finch.request(request, Explorer.Finch) | ||
|
|
||
| case response do | ||
| {:ok, %Finch.Response{status: 200, body: body}} -> | ||
| case Jason.decode(body) do | ||
| {:ok, decoded_body} -> | ||
| {:ok, decoded_body} | ||
|
|
||
| {:error, _} -> | ||
| {:error, :invalid_json} | ||
| end | ||
|
|
||
| {:ok, %Finch.Response{status: status}} -> | ||
| {:error, status} | ||
|
|
||
| {:error, reason} -> | ||
| {:error, reason} | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| defmodule AlignedProofAggregationService do | ||
| require Logger | ||
|
|
||
| @aligned_config_file System.get_env("ALIGNED_PROOF_AGG_CONFIG_FILE") | ||
|
|
||
| config_file_path = | ||
| case @aligned_config_file do | ||
| nil -> raise("ALIGNED_PROOF_AGG_CONFIG_FILE not set in .env") | ||
| file -> file | ||
| end | ||
|
|
||
| {status, config_json_string} = File.read(config_file_path) | ||
|
|
||
| case status do | ||
| :ok -> | ||
| Logger.debug("Aligned deployment file read successfully") | ||
|
|
||
| :error -> | ||
| raise( | ||
| "Config file not read successfully, make sure your .env is correctly created, and make sure Eigenlayer config file is correctly stored" | ||
| ) | ||
| end | ||
|
|
||
| @contract_address Jason.decode!(config_json_string) | ||
| |> Map.get("addresses") | ||
| |> Map.get("alignedProofAggregationService") | ||
|
|
||
| use Ethers.Contract, | ||
| abi_file: "lib/abi/AlignedProofAggregationService.json", | ||
| default_address: @contract_address | ||
|
|
||
| def get_address() do | ||
| @contract_address | ||
| end | ||
|
|
||
| def get_aggregated_proof_event(%{from_block: fromBlock, to_block: toBlock}) do | ||
| events = | ||
| AlignedProofAggregationService.EventFilters.aggregated_proof_verified(nil) | ||
| |> Ethers.get_logs(fromBlock: fromBlock, toBlock: toBlock) | ||
|
|
||
| case events do | ||
| {:ok, []} -> | ||
| {:ok, []} | ||
|
|
||
| {:ok, list} -> | ||
| {:ok, | ||
| Enum.map(list, fn x -> | ||
| data = x |> Map.get(:data) | ||
| topics_raw = x |> Map.get(:topics_raw) | ||
| block_number = x |> Map.get(:block_number) | ||
| tx_hash = x |> Map.get(:transaction_hash) | ||
|
|
||
| %{ | ||
| merkle_root: | ||
| topics_raw | ||
| |> Enum.at(1), | ||
| blob_versioned_hash: "0x" <> Base.encode16(data |> Enum.at(0), case: :lower), | ||
| block_number: block_number, | ||
| block_timestamp: get_block_timestamp(block_number), | ||
| tx_hash: tx_hash | ||
| } | ||
| end)} | ||
|
|
||
| {:error, reason} -> | ||
| {:error, reason} | ||
| end | ||
| end | ||
|
|
||
| def get_block_timestamp(block_number) do | ||
| case Ethers.Utils.get_block_timestamp(block_number) do | ||
| {:ok, timestamp} -> DateTime.from_unix!(timestamp) | ||
| {:error, error} -> raise("Error fetching block timestamp: #{error}") | ||
| end | ||
| end | ||
|
|
||
| def get_blob_data!(aggregated_proof) do | ||
| {:ok, block} = | ||
| Explorer.EthClient.get_block_by_number( | ||
| Explorer.Utils.decimal_to_hex(aggregated_proof.block_number) | ||
| ) | ||
|
|
||
| parent_beacon_block_hash = Map.get(block, "parentBeaconBlockRoot") | ||
|
|
||
| {:ok, beacon_block} = | ||
| Explorer.BeaconClient.get_block_header_by_parent_hash(parent_beacon_block_hash) | ||
|
|
||
| slot = Explorer.BeaconClient.get_block_slot(beacon_block) | ||
|
|
||
| data = | ||
| Explorer.BeaconClient.fetch_blob_by_versioned_hash!( | ||
| slot, | ||
| aggregated_proof.blob_versioned_hash | ||
| ) | ||
|
|
||
| Map.get(data, "blob") | ||
| end | ||
|
|
||
| @doc """ | ||
| Decodes blob data represented as an ASCII charlist. | ||
| """ | ||
| def decode_blob(blob_data), do: decode_blob(blob_data, [[]], 0, 0, 0) | ||
|
|
||
| defp decode_blob([], acc, _current_count, _total_count, _i), do: acc | ||
|
|
||
| defp decode_blob([head | tail], acc, current_count, total_count, i) do | ||
| # Every 64 characters (or 32 bytes) there is a 00 for padding | ||
| should_skip = rem(total_count, 64) == 0 | ||
|
|
||
| case should_skip do | ||
| true -> | ||
| [_head | tail] = tail | ||
| decode_blob(tail, acc, current_count, total_count + 2, i) | ||
|
|
||
| false -> | ||
| acc = List.update_at(acc, i, fn chunk -> chunk ++ [head] end) | ||
|
|
||
| case current_count + 1 < 64 do | ||
| true -> | ||
| decode_blob(tail, acc, current_count + 1, total_count + 1, i) | ||
|
|
||
| false -> | ||
| current_blob = Enum.at(acc, i) | ||
| # 48 is 0 in ascii | ||
| is_all_zeroes = Enum.all?(current_blob, fn x -> x == 48 end) | ||
|
|
||
| ## If the hash is all zeroed, then there are no more hashes in the blob | ||
| if is_all_zeroes do | ||
| # Drop last limiter zeroed element | ||
| Enum.drop(acc, -1) | ||
| else | ||
| decode_blob(tail, acc ++ [[]], 0, total_count + 1, i + 1) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| defmodule Explorer.EthClient do | ||
| require Logger | ||
| @rpc_url System.get_env("RPC_URL") | ||
|
|
||
| def get_block_by_number(block_number) do | ||
| eth_send("eth_getBlockByNumber", [block_number, false]) | ||
| end | ||
|
|
||
| def eth_send(method, params, id \\ 1) do | ||
MarcosNicolau marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| headers = [{"Content-Type", "application/json"}] | ||
| body = Jason.encode!(%{jsonrpc: "2.0", method: method, params: params, id: id}) | ||
| request = Finch.build(:post, @rpc_url, headers, body) | ||
| response = Finch.request(request, Explorer.Finch, []) | ||
|
|
||
| case response do | ||
| {:ok, %Finch.Response{status: 200, body: body}} -> | ||
| case Jason.decode(body) do | ||
| {:ok, %{error: error} = _} -> {:error, error.message} | ||
| {:ok, body} -> {:ok, Map.get(body, "result")} | ||
| {:error, _} -> {:error, :invalid_json} | ||
| end | ||
|
|
||
| {:ok, %Finch.Response{status: status}} -> | ||
| {:error, status} | ||
|
|
||
| {:error, reason} -> | ||
| {:error, reason} | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| defmodule AggregatedProofs do | ||
| require Logger | ||
| use Ecto.Schema | ||
| import Ecto.Changeset | ||
|
|
||
| @primary_key {:merkle_root, :string, autogenerate: false} | ||
| schema "aggregated_proofs" do | ||
| field(:blob_versioned_hash, :string) | ||
| field(:block_number, :integer) | ||
| field(:block_timestamp, :utc_datetime) | ||
| field(:tx_hash, :string) | ||
| field(:number_of_proofs, :integer) | ||
|
|
||
| has_many(:proofs_agg_mode, AggregationModeProof, | ||
| foreign_key: :merkle_root, | ||
| references: :merkle_root | ||
| ) | ||
|
|
||
| timestamps() | ||
| end | ||
|
|
||
| @doc """ | ||
| Creates a changeset based on the given `attrs`. | ||
| """ | ||
| def changeset(aggregated_proof, attrs) do | ||
| aggregated_proof | ||
| |> cast(attrs, [ | ||
| :merkle_root, | ||
| :blob_versioned_hash, | ||
| :block_number, | ||
| :block_timestamp, | ||
| :tx_hash, | ||
| :number_of_proofs | ||
| ]) | ||
| |> validate_required([ | ||
| :merkle_root, | ||
| :blob_versioned_hash, | ||
| :block_number, | ||
| :block_timestamp, | ||
| :tx_hash, | ||
| :number_of_proofs | ||
| ]) | ||
| |> unique_constraint(:merkle_root) | ||
| end | ||
|
|
||
| def insert_or_update(agg_proof) do | ||
| changeset = AggregatedProofs.changeset(%AggregatedProofs{}, agg_proof) | ||
|
|
||
| case Explorer.Repo.get_by(AggregatedProofs, merkle_root: agg_proof.merkle_root) do | ||
| nil -> | ||
| Explorer.Repo.insert(changeset) | ||
|
|
||
| existing_agg_proof -> | ||
| "Updating aggregated proof" |> Logger.debug() | ||
|
|
||
| Ecto.Changeset.change(existing_agg_proof, changeset.changes) | ||
| |> Explorer.Repo.update() | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| defmodule AggregationModeProof do | ||
| require Logger | ||
| use Ecto.Schema | ||
| import Ecto.Changeset | ||
|
|
||
| # Different from proofs.ex (we could use the same but the hashes are constructed different) | ||
| @primary_key {:id, :id, autogenerate: true} | ||
| schema "proofs_agg_mode" do | ||
| field(:merkle_root, :string) | ||
| field(:proof_hash, :string) | ||
| field(:index, :integer) | ||
|
|
||
| belongs_to(:aggregated_proof, AggregatedProof, | ||
| define_field: false, | ||
| foreign_key: :merkle_root, | ||
| references: :merkle_root, | ||
| type: :string | ||
| ) | ||
|
|
||
| timestamps() | ||
| end | ||
|
|
||
| def changeset(proof, attrs) do | ||
| proof | ||
| |> cast(attrs, [:merkle_root, :proof_hash, :index]) | ||
| |> validate_required([:merkle_root, :proof_hash, :index]) | ||
| end | ||
|
|
||
| def insert_or_update(proof) do | ||
| changeset = | ||
| AggregationModeProof.changeset(%AggregationModeProof{}, proof) | ||
|
|
||
| case( | ||
| Explorer.Repo.get_by(AggregationModeProof, proof_hash: proof.proof_hash, index: proof.index) | ||
| ) do | ||
| nil -> | ||
| Explorer.Repo.insert(changeset) | ||
|
|
||
| existing_proof -> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think it is more a general problem in the explorer, but in some moment we should improve logs
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, we should improve the logs + add a ci for formatting. |
||
| "Updating single aggregated proof" |> Logger.debug() | ||
|
|
||
| Ecto.Changeset.change(existing_proof, changeset.changes) | ||
| |> Explorer.Repo.update() | ||
| end | ||
| end | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.