Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
72d0478
feat: aggregated-mode structure + silly sp1 program
MarcosNicolau Mar 28, 2025
7a95c59
feat: interfaces + basic service to run sp1
MarcosNicolau Mar 28, 2025
0a46e9c
feat: sp1 aggregation program
MarcosNicolau Mar 28, 2025
4aa0e46
refactor: rename libs and functions
MarcosNicolau Mar 29, 2025
a0aa4af
feat: prove feature to not run prover locally
MarcosNicolau Mar 29, 2025
7065367
feat: proof aggregation service contract
MarcosNicolau Mar 30, 2025
6f2b090
feat: proof aggregation service deploy scripts
MarcosNicolau Mar 30, 2025
38eea41
chore: update anvil state json
MarcosNicolau Mar 30, 2025
993333d
refactor: use mock client for mock proves
MarcosNicolau Mar 30, 2025
69db30c
feat: verify proofs
MarcosNicolau Mar 31, 2025
943b83d
refactor: folder structure
MarcosNicolau Mar 31, 2025
051c882
refactor: move files around more
MarcosNicolau Mar 31, 2025
6ffb456
feat: proof aggregator backend
MarcosNicolau Mar 31, 2025
c674b3b
feat: merkle tree hash with keccak
MarcosNicolau Mar 31, 2025
bb7f7d2
fix: write proofs to sp1 stdin
MarcosNicolau Mar 31, 2025
554abb1
feat: verify supported proofs
MarcosNicolau Mar 31, 2025
4fa59f4
feat: sign transaction + merkle root computation
MarcosNicolau Mar 31, 2025
8885b02
feat: backend initialization
MarcosNicolau Mar 31, 2025
bcab0d1
feat: load local proofs and add to proof aggregator
MarcosNicolau Mar 31, 2025
4645f14
fix: skip when no proofs in queue
MarcosNicolau Mar 31, 2025
7ff2d4c
feat: set proof as missed on error
MarcosNicolau Mar 31, 2025
0217c99
docs: aggregation mode readme
MarcosNicolau Mar 31, 2025
7d4e14b
feat: read config from yaml file
MarcosNicolau Mar 31, 2025
ff12d58
chore: add config files + make commands
MarcosNicolau Mar 31, 2025
99a6a37
feat: s3 service
MarcosNicolau Mar 31, 2025
f9a8acc
refactor: move queue to its own struct
MarcosNicolau Mar 31, 2025
f7ed7b9
feat: proofs fetcher
MarcosNicolau Mar 31, 2025
cef6e1a
feat: main method initializer
MarcosNicolau Mar 31, 2025
6fc017d
chore: deps
MarcosNicolau Mar 31, 2025
b5c7aa3
feat: fetch logs instead of listening for events via ws
MarcosNicolau Apr 1, 2025
86a2815
refactor: remove vk from sp1 proof
MarcosNicolau Apr 1, 2025
1ab8c7e
chore: add more tracing logs
MarcosNicolau Apr 1, 2025
1be8530
feat: remove queue and returns proofs from fetcher
MarcosNicolau Apr 1, 2025
93fcccd
chore: update aligned service manager abi
MarcosNicolau Apr 1, 2025
1935850
feat: aggregated proof getter for ProofAggregationService
MarcosNicolau Apr 1, 2025
cd20c34
docs: update readme instructions
MarcosNicolau Apr 1, 2025
428ac5d
chore: update proof aggregator config
MarcosNicolau Apr 1, 2025
407808b
feat: get block number based on provided config param
MarcosNicolau Apr 1, 2025
9f94d11
feat: remove looping and run service only once
MarcosNicolau Apr 1, 2025
db5a2b0
docs: update readme instructions
MarcosNicolau Apr 1, 2025
2a4791d
feat: send blob transaction
MarcosNicolau Apr 1, 2025
18be091
fix: start_proof_aggregator_local_with_proving command
MarcosNicolau Apr 1, 2025
34a0c55
Fix readme and makefile
MauroToscano Apr 3, 2025
7efb80e
Merge branch 'feat/aggregation-mode' of github.com:yetanotherco/align…
MauroToscano Apr 3, 2025
28757e0
feat: attach blob to contract transaction instead of being separate
MarcosNicolau Apr 3, 2025
8ba0339
feat: take blob versioned hash instead of blob tx in verify contract
MarcosNicolau Apr 3, 2025
3406e43
feat(contracts): merge events into a single one with a new status field
MarcosNicolau Apr 3, 2025
1b22fd7
feat: aligned proof aggregation model and contract interface
MarcosNicolau Apr 3, 2025
36a9a93
feat: [wip] periodically fetch from ProofAggregatorService events
MarcosNicolau Apr 3, 2025
7f500c6
Merge branch 'feat/aggregation-mode' into feat/aggregation-mode-explorer
MarcosNicolau Apr 3, 2025
2a11aa0
feat: fetch logs from contract
MarcosNicolau Apr 3, 2025
0f07b05
Aggregation mode simplification (#1845)
MauroToscano Apr 3, 2025
e3883d0
Merge remote-tracking branch 'origin/feat/aggregation-mode' into feat…
MarcosNicolau Apr 3, 2025
2e11a4b
feat: beacon client to fetch blob data
MarcosNicolau Apr 4, 2025
8dc3273
fix: compilation warnings
MarcosNicolau Apr 4, 2025
22a493f
chore: .env variables
MarcosNicolau Apr 4, 2025
95337bf
feat: foreign key for agg mode proofs
MarcosNicolau Apr 7, 2025
39e4018
feat: aggregated proofs migration
MarcosNicolau Apr 7, 2025
ca56e00
feat: eth client to get block by number
MarcosNicolau Apr 7, 2025
0e71dd5
feat: blob decoding + parse events binaries
MarcosNicolau Apr 7, 2025
d5741ed
feat: periodic fetch decoded blob data
MarcosNicolau Apr 7, 2025
f95139f
refactor: more idiomatic elixir and made panics explicit
MarcosNicolau Apr 7, 2025
6e0c26b
fix: ecto schema association
MarcosNicolau Apr 7, 2025
7ad8641
chore: better tracing for agg periodic tasks
MarcosNicolau Apr 7, 2025
6831ab4
Merge remote-tracking branch 'origin/staging' into feat/aggregation-m…
MarcosNicolau Apr 7, 2025
7db5f31
fix merge
MarcosNicolau Apr 7, 2025
2712e9e
fix readme in merge
MarcosNicolau Apr 7, 2025
54281dd
refactor: get block header by hash
MarcosNicolau Apr 8, 2025
97019e9
Merge branch 'staging' into feat/aggregation-mode-explorer
MarcosNicolau Apr 8, 2025
cb06d2f
feat: update models to new contract version
MarcosNicolau Apr 8, 2025
c4f2633
feat: block_timestamp field for agg proof model
MarcosNicolau Apr 8, 2025
48be71c
feat: agg_proofs relation by uuid instead of merkle root
MarcosNicolau Apr 9, 2025
981828d
refactor: juli's comments
MarcosNicolau Apr 9, 2025
609d744
fix: edge case of repeated proofs for different proofs ids
MarcosNicolau Apr 9, 2025
3d66f25
chore: fetch aggregated proofs every 1 minute
JuArce Apr 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions explorer/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ DB_HOST=localhost

# Config file
ALIGNED_CONFIG_FILE="../contracts/script/output/devnet/alignedlayer_deployment_output.json"
ALIGNED_PROOF_AGG_CONFIG_FILE="../contracts/script/output/devnet/proof_aggregation_service_deployment_output.json"
## Don't use public nodes as blob data can be retrieved from them
BEACON_CLIENT="<beacon_client_url>"

# Debug
DEBUG_ERRORS=true
Expand Down
3 changes: 3 additions & 0 deletions explorer/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ DB_HOST=<db_host>

# Config file
ALIGNED_CONFIG_FILE="<aligned_config_file>"
ALIGNED_PROOF_AGG_CONFIG_FILE="<proof_aggregation_config_file>"
## Don't use public nodes as blob data can be retrieved from them
BEACON_CLIENT="<beacon_client_url>"

# Debug
DEBUG_ERRORS=<true|false>
Expand Down
2 changes: 2 additions & 0 deletions explorer/ecto_setup_db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export DB_USER=$DB_USER
export DB_PASS=$DB_PASS
export DB_HOST=$DB_HOST
export ALIGNED_CONFIG_FILE=$ALIGNED_CONFIG_FILE
export ALIGNED_PROOF_AGG_CONFIG_FILE=$ALIGNED_PROOF_AGG_CONFIG_FILE
export BEACON_CLIENT=$BEACON_CLIENT

mix deps.get

Expand Down
1 change: 1 addition & 0 deletions explorer/lib/abi/AlignedProofAggregationService.json

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions explorer/lib/explorer/beacon_client.ex
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

defp 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
30 changes: 30 additions & 0 deletions explorer/lib/explorer/eth_client.ex
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

defp eth_send(method, params, id \\ 1) do
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
62 changes: 62 additions & 0 deletions explorer/lib/explorer/models/aggregated_proofs.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule AggregatedProofs do
require Logger
use Ecto.Schema
import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
schema "aggregated_proofs" do
field(:merkle_root, :string)
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: :agg_proof_id,
references: :id
)

timestamps()
end

@doc """
Creates a changeset based on the given `attrs`.
"""
def changeset(aggregated_proof, attrs) do
aggregated_proof
|> cast(attrs, [
:id,
: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(:id)
end

def insert_or_update(agg_proof) do
changeset = AggregatedProofs.changeset(%AggregatedProofs{}, agg_proof)

case Explorer.Repo.get_by(AggregatedProofs, block_number: agg_proof.block_number) 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
50 changes: 50 additions & 0 deletions explorer/lib/explorer/models/aggregation_mode_proof.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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(:agg_proof_id, :binary_id)
field(:proof_hash, :string)
field(:index, :integer)

belongs_to(:aggregated_proof, AggregatedProof,
define_field: false,
foreign_key: :agg_proof_id,
references: :id,
type: :binary_id
)

timestamps()
end

def changeset(proof, attrs) do
proof
|> cast(attrs, [:agg_proof_id, :proof_hash, :index])
|> validate_required([:agg_proof_id, :proof_hash, :index])
end

def insert_or_update(proof) do
changeset =
AggregationModeProof.changeset(%AggregationModeProof{}, proof)

case(
Explorer.Repo.get_by(AggregationModeProof,
agg_proof_id: proof.agg_proof_id,
proof_hash: proof.proof_hash,
index: proof.index
)
) do
nil ->
Explorer.Repo.insert(changeset)

existing_proof ->
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Loading