|
| 1 | +defmodule AlignedProofAggregationService do |
| 2 | + require Logger |
| 3 | + |
| 4 | + @aligned_config_file System.get_env("ALIGNED_PROOF_AGG_CONFIG_FILE") |
| 5 | + |
| 6 | + config_file_path = |
| 7 | + case @aligned_config_file do |
| 8 | + nil -> raise("ALIGNED_PROOF_AGG_CONFIG_FILE not set in .env") |
| 9 | + file -> file |
| 10 | + end |
| 11 | + |
| 12 | + {status, config_json_string} = File.read(config_file_path) |
| 13 | + |
| 14 | + case status do |
| 15 | + :ok -> |
| 16 | + Logger.debug("Aligned deployment file read successfully") |
| 17 | + |
| 18 | + :error -> |
| 19 | + raise( |
| 20 | + "Config file not read successfully, make sure your .env is correctly created, and make sure Eigenlayer config file is correctly stored" |
| 21 | + ) |
| 22 | + end |
| 23 | + |
| 24 | + @contract_address Jason.decode!(config_json_string) |
| 25 | + |> Map.get("addresses") |
| 26 | + |> Map.get("alignedProofAggregationService") |
| 27 | + |
| 28 | + use Ethers.Contract, |
| 29 | + abi_file: "lib/abi/AlignedProofAggregationService.json", |
| 30 | + default_address: @contract_address |
| 31 | + |
| 32 | + def get_address() do |
| 33 | + @contract_address |
| 34 | + end |
| 35 | + |
| 36 | + def get_aggregated_proof_event(%{from_block: fromBlock, to_block: toBlock}) do |
| 37 | + events = |
| 38 | + AlignedProofAggregationService.EventFilters.aggregated_proof_verified(nil) |
| 39 | + |> Ethers.get_logs(fromBlock: fromBlock, toBlock: toBlock) |
| 40 | + |
| 41 | + case events do |
| 42 | + {:ok, []} -> |
| 43 | + {:ok, []} |
| 44 | + |
| 45 | + {:ok, list} -> |
| 46 | + {:ok, |
| 47 | + Enum.map(list, fn x -> |
| 48 | + data = x |> Map.get(:data) |
| 49 | + topics_raw = x |> Map.get(:topics_raw) |
| 50 | + block_number = x |> Map.get(:block_number) |
| 51 | + tx_hash = x |> Map.get(:transaction_hash) |
| 52 | + |
| 53 | + %{ |
| 54 | + merkle_root: |
| 55 | + topics_raw |
| 56 | + |> Enum.at(1), |
| 57 | + blob_versioned_hash: "0x" <> Base.encode16(data |> Enum.at(0), case: :lower), |
| 58 | + block_number: block_number, |
| 59 | + block_timestamp: get_block_timestamp(block_number), |
| 60 | + tx_hash: tx_hash |
| 61 | + } |
| 62 | + end)} |
| 63 | + |
| 64 | + {:error, reason} -> |
| 65 | + {:error, reason} |
| 66 | + end |
| 67 | + end |
| 68 | + |
| 69 | + def get_block_timestamp(block_number) do |
| 70 | + case Ethers.Utils.get_block_timestamp(block_number) do |
| 71 | + {:ok, timestamp} -> DateTime.from_unix!(timestamp) |
| 72 | + {:error, error} -> raise("Error fetching block timestamp: #{error}") |
| 73 | + end |
| 74 | + end |
| 75 | + |
| 76 | + def get_blob_data!(aggregated_proof) do |
| 77 | + {:ok, block} = |
| 78 | + Explorer.EthClient.get_block_by_number( |
| 79 | + Explorer.Utils.decimal_to_hex(aggregated_proof.block_number) |
| 80 | + ) |
| 81 | + |
| 82 | + parent_beacon_block_hash = Map.get(block, "parentBeaconBlockRoot") |
| 83 | + |
| 84 | + {:ok, beacon_block} = |
| 85 | + Explorer.BeaconClient.get_block_header_by_parent_hash(parent_beacon_block_hash) |
| 86 | + |
| 87 | + slot = Explorer.BeaconClient.get_block_slot(beacon_block) |
| 88 | + |
| 89 | + data = |
| 90 | + Explorer.BeaconClient.fetch_blob_by_versioned_hash!( |
| 91 | + slot, |
| 92 | + aggregated_proof.blob_versioned_hash |
| 93 | + ) |
| 94 | + |
| 95 | + Map.get(data, "blob") |
| 96 | + end |
| 97 | + |
| 98 | + @doc """ |
| 99 | + Decodes blob data represented as an ASCII charlist. |
| 100 | + """ |
| 101 | + def decode_blob(blob_data), do: decode_blob(blob_data, [[]], 0, 0, 0) |
| 102 | + |
| 103 | + defp decode_blob([], acc, _current_count, _total_count, _i), do: acc |
| 104 | + |
| 105 | + defp decode_blob([head | tail], acc, current_count, total_count, i) do |
| 106 | + # Every 64 characters (or 32 bytes) there is a 00 for padding |
| 107 | + should_skip = rem(total_count, 64) == 0 |
| 108 | + |
| 109 | + case should_skip do |
| 110 | + true -> |
| 111 | + [_head | tail] = tail |
| 112 | + decode_blob(tail, acc, current_count, total_count + 2, i) |
| 113 | + |
| 114 | + false -> |
| 115 | + acc = List.update_at(acc, i, fn chunk -> chunk ++ [head] end) |
| 116 | + |
| 117 | + case current_count + 1 < 64 do |
| 118 | + true -> |
| 119 | + decode_blob(tail, acc, current_count + 1, total_count + 1, i) |
| 120 | + |
| 121 | + false -> |
| 122 | + current_blob = Enum.at(acc, i) |
| 123 | + # 48 is 0 in ascii |
| 124 | + is_all_zeroes = Enum.all?(current_blob, fn x -> x == 48 end) |
| 125 | + |
| 126 | + ## If the hash is all zeroed, then there are no more hashes in the blob |
| 127 | + if is_all_zeroes do |
| 128 | + # Drop last limiter zeroed element |
| 129 | + Enum.drop(acc, -1) |
| 130 | + else |
| 131 | + decode_blob(tail, acc ++ [[]], 0, total_count + 1, i + 1) |
| 132 | + end |
| 133 | + end |
| 134 | + end |
| 135 | + end |
| 136 | +end |
0 commit comments