Skip to content

Commit 4755fb0

Browse files
glpecileuri-99
andauthored
feat(explorer): add cost per proof of batch (#617)
Co-authored-by: Urix <43704209+uri-99@users.noreply.github.com>
1 parent 495ac86 commit 4755fb0

13 files changed

Lines changed: 347 additions & 100 deletions

File tree

explorer/lib/explorer/aligned_layer_service_manager.ex

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,52 @@ defmodule AlignedLayerServiceManager do
1212
nil -> raise("Invalid ENVIRONMENT var in .env")
1313
end
1414

15-
config_file_path = case @aligned_config_file do
16-
nil -> raise("ALIGNED_CONFIG_FILE not set in .env")
17-
file -> file
15+
config_file_path =
16+
case @aligned_config_file do
17+
nil -> raise("ALIGNED_CONFIG_FILE not set in .env")
18+
file -> file
19+
end
20+
21+
payment_service_path =
22+
"../contracts/script/deploy/config/#{@environment}/batcher-payment-service.#{@environment}.config.json"
23+
24+
{status_aligned_config, config_json_string} = File.read(config_file_path)
25+
{status_payment_service, payment_service_json_string} = File.read(payment_service_path)
26+
27+
case status_aligned_config do
28+
:ok ->
29+
Logger.debug("Aligned config file read successfully")
30+
31+
:error ->
32+
raise(
33+
"Config file not read successfully, did you run make create-env? If you did,\n make sure Alignedlayer config file is correctly stored"
34+
)
1835
end
1936

20-
{status, config_json_string} = File.read(config_file_path)
37+
case status_payment_service do
38+
:ok ->
39+
Logger.debug("Payment service file read successfully")
2140

22-
case status do
23-
:ok -> Logger.debug("File read successfully")
24-
:error -> raise("Config file not read successfully, did you run make create-env? If you did,\n make sure Alignedlayer config file is correctly stored")
41+
:error ->
42+
raise(
43+
"Payment service file not read successfully, did you run make create-env? If you did,\n make sure Alignedlayer config file is correctly stored"
44+
)
2545
end
2646

2747
@aligned_layer_service_manager_address Jason.decode!(config_json_string)
2848
|> Map.get("addresses")
2949
|> Map.get("alignedLayerServiceManager")
3050

31-
@first_block (
32-
case @environment do
33-
"devnet" -> 0
34-
"holesky" -> 1728056
35-
"mainnet" -> 20020000
36-
_ -> raise("Invalid environment")
37-
end
38-
)
51+
@gas_per_proof Jason.decode!(payment_service_json_string)
52+
|> Map.get("amounts")
53+
|> Map.get("gasPerProof")
54+
55+
@first_block (case @environment do
56+
"devnet" -> 0
57+
"holesky" -> 1_728_056
58+
"mainnet" -> 20_020_000
59+
_ -> raise("Invalid environment")
60+
end)
3961

4062
use Ethers.Contract,
4163
abi_file: "lib/abi/AlignedLayerServiceManager.json",
@@ -58,12 +80,13 @@ defmodule AlignedLayerServiceManager do
5880
case events do
5981
{:ok, []} -> []
6082
{:ok, list} -> Enum.map(list, &extract_new_batch_event_info/1)
61-
{:error, reason } -> raise("Error fetching events: #{Map.get(reason, "message")}")
83+
{:error, reason} -> raise("Error fetching events: #{Map.get(reason, "message")}")
6284
end
6385
end
6486

6587
def extract_new_batch_event_info(event) do
6688
new_batch = parse_new_batch_event(event)
89+
6790
{:ok,
6891
%NewBatchInfo{
6992
address: event |> Map.get(:address),
@@ -97,10 +120,14 @@ defmodule AlignedLayerServiceManager do
97120
def extract_batch_response({_status, %NewBatchInfo{} = batch_creation}) do
98121
created_batch = batch_creation.new_batch
99122
was_batch_responded = is_batch_responded(created_batch.batchMerkleRoot)
100-
batch_response = case was_batch_responded do
101-
true -> fetch_batch_response(created_batch.batchMerkleRoot)
102-
false -> %{block_number: nil, transaction_hash: nil, block_timestamp: nil} #was not verified, fill with nils
103-
end
123+
124+
batch_response =
125+
case was_batch_responded do
126+
true -> fetch_batch_response(created_batch.batchMerkleRoot)
127+
# was not verified, fill with nils
128+
false -> %{block_number: nil, transaction_hash: nil, block_timestamp: nil}
129+
end
130+
104131
%BatchDB{
105132
merkle_root: created_batch.batchMerkleRoot,
106133
data_pointer: created_batch.batchDataPointer,
@@ -112,17 +139,23 @@ defmodule AlignedLayerServiceManager do
112139
response_transaction_hash: batch_response.transaction_hash,
113140
response_timestamp: batch_response.block_timestamp,
114141
amount_of_proofs: nil,
115-
proof_hashes: nil
142+
proof_hashes: nil,
143+
cost_per_proof: get_cost_per_proof()
116144
}
117145
end
118146

119-
#for existing but unverified batches
147+
# for existing but unverified batches
120148
def extract_batch_response(%Batches{} = unverified_batch) do
121149
was_batch_responded = is_batch_responded(unverified_batch.merkle_root)
150+
122151
case was_batch_responded do
123-
false -> nil # Do nothing since unverified batch was not yet verified
152+
# Do nothing since unverified batch was not yet verified
153+
false ->
154+
nil
155+
124156
true ->
125157
batch_response = fetch_batch_response(unverified_batch.merkle_root)
158+
126159
%BatchDB{
127160
merkle_root: unverified_batch.merkle_root,
128161
data_pointer: unverified_batch.data_pointer,
@@ -134,6 +167,7 @@ defmodule AlignedLayerServiceManager do
134167
response_transaction_hash: batch_response.transaction_hash,
135168
response_timestamp: batch_response.block_timestamp,
136169
amount_of_proofs: unverified_batch.amount_of_proofs,
170+
cost_per_proof: unverified_batch.cost_per_proof,
137171
proof_hashes: nil #don't need this value to update an existing but unverified batch, it is on another table
138172
}
139173
end
@@ -150,7 +184,7 @@ defmodule AlignedLayerServiceManager do
150184
def get_batch_verified_events(%{merkle_root: merkle_root}) do
151185
event =
152186
AlignedLayerServiceManager.EventFilters.batch_verified(Utils.string_to_bytes32(merkle_root))
153-
|> Ethers.get_logs(fromBlock: @first_block)
187+
|> Ethers.get_logs(fromBlock: @first_block)
154188

155189
case event do
156190
{:error, reason} -> {:error, reason}
@@ -179,4 +213,18 @@ defmodule AlignedLayerServiceManager do
179213
end
180214
end
181215

216+
def get_current_gas_price() do
217+
case Ethers.current_gas_price() do
218+
{:ok, gas_price} ->
219+
gas_price
220+
{:error, error} -> raise("Error fetching gas price: #{error}")
221+
end
222+
end
223+
224+
def get_cost_per_proof() do
225+
case Integer.parse(@gas_per_proof) do
226+
{value, _} -> value * get_current_gas_price()
227+
:error -> raise("Error parsing @gas_per_proof")
228+
end
229+
end
182230
end

explorer/lib/explorer/application.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Explorer.Application do
99
def start(_type, _args) do
1010
children = [
1111
ExplorerWeb.Telemetry,
12+
{Cachex, name: :eth_price_cache},
1213
{DNSCluster, query: Application.get_env(:explorer, :dns_cluster_query) || :ignore},
1314
{Phoenix.PubSub, name: Explorer.PubSub},
1415
# Start the Ecto db repository
@@ -29,6 +30,7 @@ defmodule Explorer.Application do
2930
{Explorer.Periodically, []},
3031
{Mutex, name: BatchMutex, meta: "Used to prevent concurrent downloads"}
3132
]
33+
3234
periodic_opts = [strategy: :one_for_all, name: Explorer.Periodically.Supervisor]
3335
Supervisor.start_link(periodic_children, periodic_opts)
3436
end

explorer/lib/explorer/avs_directory.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule AVSDirectory do
99
{status, config_json_string} = File.read(file_path)
1010

1111
case status do
12-
:ok -> Logger.debug("File read successfully")
12+
:ok -> Logger.debug("Eigenlayer deployment file read successfully")
1313
:error -> raise("Config file not read successfully, did you run make create-env? If you did,\n make sure Eigenlayer config file is correctly stored")
1414
end
1515

explorer/lib/explorer/models/batch_structs.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ defmodule BatchDB do
2929
:submission_block_number,
3030
:submission_transaction_hash,
3131
:submission_timestamp,
32-
:proof_hashes
32+
:proof_hashes,
33+
:cost_per_proof
3334
]
3435
defstruct [
3536
:merkle_root,
@@ -42,6 +43,7 @@ defmodule BatchDB do
4243
:response_transaction_hash,
4344
:response_timestamp,
4445
:data_pointer,
45-
:proof_hashes
46+
:proof_hashes,
47+
:cost_per_proof
4648
]
4749
end

explorer/lib/explorer/models/batches.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ defmodule Batches do
1414
field :response_transaction_hash, :string
1515
field :response_timestamp, :utc_datetime
1616
field :data_pointer, :string
17+
# Elixir's default integer type effectively serves the same purpose as bigint, handling arbitrarily large integers out of the box.
18+
field :cost_per_proof, :integer
1719

1820
timestamps()
1921
end
2022

2123
@doc false
2224
def changeset(new_batch, updates) do
2325
new_batch
24-
|> 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])
25-
|> validate_required([:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash])
26+
|> 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, :cost_per_proof])
27+
|> validate_required([:merkle_root, :amount_of_proofs, :is_verified, :submission_block_number, :submission_transaction_hash, :cost_per_proof])
2628
|> validate_format(:merkle_root, ~r/0x[a-fA-F0-9]{64}/)
2729
|> unique_constraint(:merkle_root)
2830
|> validate_number(:amount_of_proofs, greater_than: 0)
@@ -31,6 +33,7 @@ defmodule Batches do
3133
|> validate_format(:submission_transaction_hash, ~r/0x[a-fA-F0-9]{64}/)
3234
|> validate_number(:response_block_number, greater_than: 0)
3335
|> validate_format(:response_transaction_hash, ~r/0x[a-fA-F0-9]{64}/)
36+
|> validate_number(:cost_per_proof, greater_than: 0)
3437
end
3538

3639
def cast_to_batches(%BatchDB{} = batch_db) do
@@ -45,6 +48,7 @@ defmodule Batches do
4548
response_transaction_hash: batch_db.response_transaction_hash,
4649
response_timestamp: batch_db.response_timestamp,
4750
data_pointer: batch_db.data_pointer,
51+
cost_per_proof: batch_db.cost_per_proof
4852
}
4953
end
5054

explorer/lib/explorer/periodically.ex

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,38 @@ defmodule Explorer.Periodically do
1212
end
1313

1414
def send_work() do
15-
seconds = 12 # once per block
16-
:timer.send_interval(seconds * 1000, :work) # send every n seconds
15+
# once per block
16+
seconds = 12
17+
# send every n seconds
18+
:timer.send_interval(seconds * 1000, :work)
1719
end
1820

1921
def handle_info(:work, count) do
2022
# Reads and process last n blocks for new batches or batch changes
21-
read_block_qty = 8
22-
latest_block_number = AlignedLayerServiceManager.get_latest_block_number()
23-
read_from_block = max(0, latest_block_number - read_block_qty)
23+
read_block_qty = 8
24+
latest_block_number = AlignedLayerServiceManager.get_latest_block_number()
25+
read_from_block = max(0, latest_block_number - read_block_qty)
2426

2527
Task.start(fn -> process_blocks_from_to(read_from_block, latest_block_number) end)
2628

2729
# Gets previous unverified batches and checks if they were verified
28-
run_every_n_iterations = 8
29-
new_count = rem(count + 1, run_every_n_iterations)
30-
if new_count == 0 do
31-
Task.start(&process_unverified_batches/0)
32-
end
30+
run_every_n_iterations = 8
31+
new_count = rem(count + 1, run_every_n_iterations)
32+
if new_count == 0 do
33+
Task.start(&process_unverified_batches/0)
34+
end
3335

3436
{:noreply, new_count}
3537
end
3638

3739
def process_blocks_from_to(fromBlock, toBlock) do
3840
"Processing from block #{fromBlock} to block #{toBlock}..." |> IO.inspect()
41+
3942
try do
4043
AlignedLayerServiceManager.get_new_batch_events(%{fromBlock: fromBlock, toBlock: toBlock})
41-
|> Enum.map(&AlignedLayerServiceManager.extract_batch_response/1)
42-
# This function will avoid processing a batch taken by another process
43-
|> Enum.map(&process_batch_if_not_in_other_process/1)
44-
44+
|> Enum.map(&AlignedLayerServiceManager.extract_batch_response/1)
45+
# This function will avoid processing a batch taken by another process
46+
|> Enum.map(&process_batch_if_not_in_other_process/1)
4547
rescue
4648
error -> IO.puts("An error occurred during batch processing:\n#{inspect(error)}")
4749
end
@@ -55,47 +57,58 @@ defmodule Explorer.Periodically do
5557
# one lock for each batch
5658
case Mutex.lock(BatchMutex, {batch.merkle_root}) do
5759
{:error, :busy} ->
58-
"Batch already being processed: #{batch.merkle_root}" |> IO.inspect
60+
"Batch already being processed: #{batch.merkle_root}" |> IO.inspect()
5961
nil
6062

6163
{:ok, lock} ->
62-
"Processing batch: #{batch.merkle_root}" |> IO.inspect
64+
"Processing batch: #{batch.merkle_root}" |> IO.inspect()
65+
6366
{batch_changeset, proofs} =
6467
batch
65-
|> Utils.extract_info_from_data_pointer
66-
|> Batches.generate_changesets
68+
|> Utils.extract_info_from_data_pointer()
69+
|> Batches.generate_changesets()
6770

6871
Batches.insert_or_update(batch_changeset, proofs)
69-
|> case do
70-
{:ok, _} ->
71-
IO.puts("Broadcasting update_views")
72-
PubSub.broadcast(Explorer.PubSub, "update_views", %{})
73-
{:error, error} ->
74-
IO.puts("Some error in DB operation, not broadcasting update_views")
75-
IO.inspect(error)
76-
nil -> nil #no changes in DB
77-
end
78-
79-
"Done processing batch: #{batch.merkle_root}" |> IO.inspect
72+
|> case do
73+
{:ok, _} ->
74+
PubSub.broadcast(Explorer.PubSub, "update_views", %{
75+
merkle_root: batch.merkle_root,
76+
eth_usd:
77+
case EthConverter.get_eth_price_usd() do
78+
{:ok, eth_usd_price} -> eth_usd_price
79+
{:error, _error} -> :empty
80+
end
81+
})
82+
83+
{:error, error} ->
84+
IO.puts("Some error in DB operation, not broadcasting update_views")
85+
IO.inspect(error)
86+
87+
# no changes in DB
88+
nil ->
89+
nil
90+
end
91+
92+
"Done processing batch: #{batch.merkle_root}" |> IO.inspect()
8093
Mutex.release(BatchMutex, lock)
8194
end
8295
end
8396

8497
defp process_unverified_batches() do
85-
"verifying previous unverified batches..." |> IO.inspect()
98+
"Verifying previous unverified batches..." |> IO.inspect()
8699
unverified_batches = Batches.get_unverified_batches()
87100

88101
array_of_changest_tuples =
89102
unverified_batches
90-
|> Enum.map(&AlignedLayerServiceManager.extract_batch_response/1)
91-
|> Enum.reject(&is_nil/1)
92-
|> Enum.map(&Batches.generate_changesets/1)
103+
|> Enum.map(&AlignedLayerServiceManager.extract_batch_response/1)
104+
|> Enum.reject(&is_nil/1)
105+
|> Enum.map(&Batches.generate_changesets/1)
93106

94107
Enum.map(
95108
array_of_changest_tuples,
96109
fn {batch_changeset, proofs} ->
97110
Batches.insert_or_update(batch_changeset, proofs)
98-
end)
111+
end
112+
)
99113
end
100-
101114
end

0 commit comments

Comments
 (0)