Skip to content

Commit 4c9474f

Browse files
committed
Merge branch 'main' of github.com:lambdaclass/lambda_ethereum_consensus into refactor-pending-blocks
2 parents 9cb0752 + 29e3eaa commit 4c9474f

File tree

25 files changed

+622
-119
lines changed

25 files changed

+622
-119
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ jobs:
169169
run: mix compile --warnings-as-errors
170170
- name: Run the node
171171
# NOTE: this starts and then stops the application. It should catch simple runtime errors
172-
run: mix run -- --checkpoint-sync https://sync-mainnet.beaconcha.in/
172+
run: mix run -- --checkpoint-sync-url https://sync-mainnet.beaconcha.in/
173173

174174
test:
175175
name: Test

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,11 @@ iex: compile-all
116116

117117
#▶️ checkpoint-sync: @ Run an interactive terminal using checkpoint sync.
118118
checkpoint-sync: compile-all
119-
iex -S mix run -- --checkpoint-sync https://sync-mainnet.beaconcha.in/
119+
iex -S mix run -- --checkpoint-sync-url https://sync-mainnet.beaconcha.in/
120120

121121
#▶️ sepolia: @ Run an interactive terminal using sepolia network
122122
sepolia: compile-all
123-
iex -S mix run -- --checkpoint-sync https://sepolia.beaconstate.info --network sepolia
123+
iex -S mix run -- --checkpoint-sync-url https://sepolia.beaconstate.info --network sepolia
124124

125125
#🔴 test: @ Run tests
126126
test: compile-all

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ The iex terminal can be closed by pressing ctrl+c two times.
7878
### Checkpoint Sync
7979

8080
You can also sync from a checkpoint given by a trusted third-party.
81-
For that, get the URL that serves the checkpoint, and pass it to the node with the "--checkpoint-sync" flag:
81+
You can specify a URL to fetch it from with the "--checkpoint-sync-url" flag:
8282

8383
```shell
84-
iex -S mix run -- --checkpoint-sync <your_url_here>
84+
iex -S mix run -- --checkpoint-sync-url <your_url_here>
8585
```
8686

8787
Some public endpoints can be found in [eth-clients.github.io/checkpoint-sync-endpoints](https://eth-clients.github.io/checkpoint-sync-endpoints/).

bench/ssz.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Benchee.run(
3535
time: 5
3636
)
3737

38-
## Benchmark Merkleization
38+
## Benchmark Merkleization
3939

4040
list = Stream.cycle([65_535]) |> Enum.take(316)
4141
schema = {:list, {:int, 16}, 1024}

config/runtime.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Config
22

33
switches = [
44
network: :string,
5-
checkpoint_sync: :string,
5+
checkpoint_sync_url: :string,
66
execution_endpoint: :string,
77
execution_jwt: :string,
88
mock_execution: :boolean,
@@ -23,12 +23,12 @@ if not is_testing and not Enum.empty?(remaining_args) do
2323
end
2424

2525
network = Keyword.get(args, :network, "mainnet")
26-
checkpoint_sync = Keyword.get(args, :checkpoint_sync)
26+
checkpoint_sync_url = Keyword.get(args, :checkpoint_sync_url)
2727
execution_endpoint = Keyword.get(args, :execution_endpoint, "http://localhost:8551")
2828
jwt_path = Keyword.get(args, :execution_jwt)
2929

3030
config :lambda_ethereum_consensus, LambdaEthereumConsensus.ForkChoice,
31-
checkpoint_sync: checkpoint_sync
31+
checkpoint_sync_url: checkpoint_sync_url
3232

3333
configs_per_network = %{
3434
"minimal" => MinimalConfig,

lib/lambda_ethereum_consensus/application.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ defmodule LambdaEthereumConsensus.Application do
4949

5050
def checkpoint_sync_url do
5151
Application.fetch_env!(:lambda_ethereum_consensus, LambdaEthereumConsensus.ForkChoice)
52-
|> Keyword.fetch!(:checkpoint_sync)
52+
|> Keyword.fetch!(:checkpoint_sync_url)
5353
end
5454

5555
defp get_operation_mode do

lib/lambda_ethereum_consensus/beacon/beacon_node.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconNode do
2626

2727
:not_found ->
2828
Logger.error(
29-
"[Sync] No initial state or block found. Please specify the URL to fetch them from via the --checkpoint-sync flag"
29+
"[Sync] No initial state or block found. Please specify the URL to fetch them from via the --checkpoint-sync-url flag"
3030
)
3131

3232
System.stop(1)

lib/lambda_ethereum_consensus/beacon/checkpoint_sync.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule LambdaEthereumConsensus.Beacon.CheckpointSync do
1010
@doc """
1111
Safely retrieves the last finalized state and block
1212
"""
13-
@spec get_state(String.t()) ::
13+
@spec get_finalized_block_and_state(String.t()) ::
1414
{:ok, {Types.BeaconState.t(), Types.SignedBeaconBlock.t()}} | {:error, any()}
1515
def get_finalized_block_and_state(url) do
1616
tasks = [Task.async(__MODULE__, :get_state, [url]), Task.async(__MODULE__, :get_block, [url])]

lib/lambda_ethereum_consensus/execution/engine_api/mocked.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ defmodule LambdaEthereumConsensus.Execution.EngineApi.Mocked do
2020

2121
@spec forkchoice_updated(map, map | any) :: {:ok, any} | {:error, any}
2222
def forkchoice_updated(_forkchoice_state, _payload_attributes) do
23-
{:ok, %{"payload_id" => nil, payload_status: %{"status" => "SYNCING"}}}
23+
{:ok, %{"payload_id" => nil, "payload_status" => %{"status" => "SYNCING"}}}
2424
end
2525
end

lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ defmodule LambdaEthereumConsensus.ForkChoice do
77
require Logger
88

99
alias LambdaEthereumConsensus.Beacon.BeaconChain
10-
alias LambdaEthereumConsensus.Execution.ExecutionClient
1110
alias LambdaEthereumConsensus.ForkChoice.{Handlers, Helpers}
1211
alias LambdaEthereumConsensus.Store.Blocks
1312
alias Types.Attestation
@@ -166,21 +165,11 @@ defmodule LambdaEthereumConsensus.ForkChoice do
166165
@spec recompute_head(Store.t()) :: :ok
167166
def recompute_head(store) do
168167
{:ok, head_root} = Helpers.get_head(store)
169-
170168
head_block = Blocks.get_block!(head_root)
171-
head_execution_hash = head_block.body.execution_payload.block_hash
169+
170+
Handlers.notify_forkchoice_update(store, head_block)
172171

173172
finalized_checkpoint = store.finalized_checkpoint
174-
finalized_block = Blocks.get_block!(store.finalized_checkpoint.root)
175-
finalized_execution_hash = finalized_block.body.execution_payload.block_hash
176-
177-
# TODO: do someting with the result from the execution client
178-
# TODO: compute safe block hash
179-
ExecutionClient.notify_forkchoice_updated(
180-
head_execution_hash,
181-
finalized_execution_hash,
182-
finalized_execution_hash
183-
)
184173

185174
BeaconChain.update_fork_choice_cache(
186175
head_root,

lib/lambda_ethereum_consensus/fork_choice/handlers.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ defmodule LambdaEthereumConsensus.ForkChoice.Handlers do
33
Handlers that update the fork choice store.
44
"""
55

6+
alias LambdaEthereumConsensus.Execution.ExecutionClient
67
alias LambdaEthereumConsensus.StateTransition
78
alias LambdaEthereumConsensus.StateTransition.{Accessors, EpochProcessing, Misc, Predicates}
89
alias LambdaEthereumConsensus.Store.Blocks
@@ -184,6 +185,21 @@ defmodule LambdaEthereumConsensus.ForkChoice.Handlers do
184185
end
185186
end
186187

188+
def notify_forkchoice_update(store, head_block) do
189+
head_execution_hash = head_block.body.execution_payload.block_hash
190+
191+
finalized_block = Blocks.get_block!(store.finalized_checkpoint.root)
192+
finalized_execution_hash = finalized_block.body.execution_payload.block_hash
193+
194+
# TODO: do someting with the result from the execution client
195+
# TODO: compute safe block hash
196+
ExecutionClient.notify_forkchoice_updated(
197+
head_execution_hash,
198+
finalized_execution_hash,
199+
finalized_execution_hash
200+
)
201+
end
202+
187203
### Private functions ###
188204

189205
# Update checkpoints in store if necessary

lib/lambda_ethereum_consensus/logger/console_logger.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule ConsoleLogger do
1919
defp level_color(:info), do: :green
2020
defp level_color(:warning), do: :yellow
2121
defp level_color(:error), do: :red
22-
defp level_color(_), do: :default
22+
defp level_color(_), do: :default_color
2323

2424
defp format_level(level) do
2525
upcased = level |> Atom.to_string() |> String.upcase()

lib/lambda_ethereum_consensus/state_transition/misc.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
170170
first_8_bytes
171171
end
172172

173-
@spec uint_to_bytes(non_neg_integer(), 8 | 32 | 64) :: binary()
173+
@spec uint_to_bytes(non_neg_integer(), 8 | 16 | 32 | 64) :: binary()
174174
def uint_to_bytes(value, size) do
175175
# Converts an unsigned integer value to a bytes value
176176
<<value::unsigned-integer-little-size(size)>>

lib/snappy_ex.ex

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
defmodule SnappyEx do
2+
@moduledoc """
3+
Encoder/decoder implementation for the [Snappy framing format](https://github.com/google/snappy/blob/main/framing_format.txt)
4+
"""
5+
import Bitwise
6+
7+
@bit_mask_32 2 ** 32 - 1
8+
@chunk_size_limit 65_540
9+
10+
@stream_identifier "sNaPpY"
11+
12+
@id_compressed_data 0x00
13+
@id_uncompressed_data 0x01
14+
@id_padding 0xFE
15+
@id_stream_identifier 0xFF
16+
17+
@ids_payload_chunks [@id_compressed_data, @id_uncompressed_data]
18+
@ids_reserved_unskippable_chunks 0x02..0x7F
19+
@ids_reserved_skippable_chunks 0x80..0xFD
20+
21+
##########################
22+
### Public API
23+
##########################
24+
25+
@doc """
26+
Compresses the given data.
27+
Returns the compressed data.
28+
29+
## Examples
30+
31+
iex> SnappyEx.compress("")
32+
<<0xFF, 6::little-size(24)>> <> "sNaPpY"
33+
"""
34+
@spec compress(binary()) :: binary()
35+
def compress(data) when is_binary(data) do
36+
# TODO: implement
37+
<<@id_stream_identifier, 6::little-size(24)>> <> @stream_identifier
38+
end
39+
40+
@doc """
41+
Uncompresses a given stream.
42+
Returns a result tuple with the uncompressed data or an error message.
43+
44+
## Examples
45+
46+
iex> SnappyEx.decompress(<<0xFF, 6::little-size(24)>> <> "sNaPpY")
47+
{:ok, ""}
48+
"""
49+
@spec decompress(nonempty_binary()) :: {:ok, binary()} | {:error, String.t()}
50+
def decompress(<<@id_stream_identifier>> <> _ = chunks), do: decompress_frames(chunks, <<>>)
51+
52+
@spec decompress(<<>>) :: {:error, String.t()}
53+
def decompress(chunks) when is_binary(chunks), do: {:error, "no stream identifier at beginning"}
54+
55+
@spec compute_checksum(binary()) :: Types.uint32()
56+
def compute_checksum(data) when is_binary(data) do
57+
checksum = Crc32c.calc!(data)
58+
59+
# the crc32c checksum of the uncompressed data is masked before inserted into the
60+
# frame using masked_checksum = ((checksum >> 15) | (checksum << 17)) + 0xa282ead8
61+
(checksum >>> 15 ||| checksum <<< 17) + 0xA282EAD8 &&& @bit_mask_32
62+
end
63+
64+
##########################
65+
### Private Functions
66+
##########################
67+
68+
defp decompress_frames("", acc), do: {:ok, acc}
69+
70+
defp decompress_frames(chunks, acc) do
71+
with {:ok, {id, data, remaining_chunks}} <- process_chunk_metadata(chunks),
72+
{:ok, new_acc} <- parse_chunk(acc, id, data) do
73+
decompress_frames(remaining_chunks, new_acc)
74+
end
75+
end
76+
77+
# chunk layout: 1-byte chunk_id, 3-bytes chunk_size, remaining chunks if any data present.
78+
defp process_chunk_metadata(chunks) when byte_size(chunks) < 4,
79+
do: {:error, "header too small"}
80+
81+
defp process_chunk_metadata(<<_id::size(8), size::little-size(24), rest::binary>>)
82+
when byte_size(rest) < size,
83+
do: {:error, "missing data in chunk. expected: #{byte_size(rest)}. got: #{size}"}
84+
85+
defp process_chunk_metadata(<<id::size(8), size::little-size(24), rest::binary>>) do
86+
<<chunk::binary-size(size), remaining_chunks::binary>> = rest
87+
{:ok, {id, chunk, remaining_chunks}}
88+
end
89+
90+
# Stream identifier
91+
# NOTE: it can appear more than once, and must be validated each time
92+
defp parse_chunk(acc, @id_stream_identifier, @stream_identifier), do: {:ok, acc}
93+
defp parse_chunk(_, @id_stream_identifier, _), do: {:error, "invalid stream identifier"}
94+
95+
# Data-carrying chunks (compressed or uncompressed)
96+
defp parse_chunk(_acc, id, data)
97+
when id in @ids_payload_chunks and byte_size(data) > @chunk_size_limit,
98+
do: {:error, "chunk is bigger than limit"}
99+
100+
defp parse_chunk(acc, id, data) when id in @ids_payload_chunks do
101+
<<checksum::little-size(32), compressed_data::binary>> = data
102+
103+
with {:ok, uncompressed_data} <- decompress_payload(id, compressed_data),
104+
:ok <- verify_checksum(uncompressed_data, checksum) do
105+
{:ok, <<acc::binary, uncompressed_data::binary>>}
106+
end
107+
end
108+
109+
# Skippable chunks (padding or reserved)
110+
defp parse_chunk(acc, id, _data)
111+
when id == @id_padding or id in @ids_reserved_skippable_chunks,
112+
do: {:ok, acc}
113+
114+
# Reserved unskippable chunks
115+
defp parse_chunk(_acc, id, _data) when id in @ids_reserved_unskippable_chunks,
116+
do: {:error, "unskippable chunk of type: #{id}"}
117+
118+
defp decompress_payload(@id_compressed_data, data), do: :snappyer.decompress(data)
119+
defp decompress_payload(@id_uncompressed_data, data), do: {:ok, data}
120+
121+
defp verify_checksum(data, checksum) do
122+
if checksum == compute_checksum(data),
123+
do: :ok,
124+
else: {:error, "invalid checksum"}
125+
end
126+
end

0 commit comments

Comments
 (0)