Skip to content

Commit b5c75b4

Browse files
committed
Merge branch 'main' into validator-manager-genserver-removal
2 parents 9985081 + 40faca6 commit b5c75b4

35 files changed

+1634
-866
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ checkpoint-sync: compile-all
166166

167167
#▶️ sepolia: @ Run an interactive terminal using sepolia network
168168
sepolia: compile-all
169-
iex -S mix run -- --checkpoint-sync-url https://sepolia.beaconstate.info --network sepolia
169+
iex -S mix run -- --checkpoint-sync-url https://sepolia.beaconstate.info --network sepolia --metrics
170170

171171
#▶️ holesky: @ Run an interactive terminal using holesky network
172172
holesky: compile-all

README.md

Lines changed: 137 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,56 @@ make dialyzer # Runs type-checker
121121
Source code can be formatted using `make fmt`.
122122
This formats not only the Elixir code, but also the code under [`native/`](./native/).
123123

124+
### Consensus spec tests
125+
126+
You can run all of them with:
127+
128+
```shell
129+
make spec-test
130+
```
131+
132+
Or only run those of a specific config with:
133+
134+
```shell
135+
make spec-test-config-`config`
136+
137+
# Some examples
138+
make spec-test-config-mainnet
139+
make spec-test-config-minimal
140+
make spec-test-config-general
141+
```
142+
143+
Or by a single runner in all configs, with:
144+
145+
```shell
146+
make spec-test-runner-`runner`
147+
148+
# Some examples
149+
make spec-test-runner-ssz_static
150+
make spec-test-runner-bls
151+
make spec-test-runner-operations
152+
```
153+
154+
The complete list of test runners can be found [here](https://github.com/ethereum/consensus-specs/tree/dev/tests/formats).
155+
156+
If you want to specify both a config and a runner:
157+
158+
```shell
159+
make spec-test-mainnet-operations
160+
make spec-test-minimal-epoch_processing
161+
make spec-test-general-bls
162+
```
163+
164+
More advanced filtering (e.g. by fork or handler) will be re-added again, but if you want to only run a specific test, you can always do that manually with:
165+
166+
```shell
167+
mix test --no-start test/generated/<config>/<fork>/<runner>.exs:<line_of_your_testcase>
168+
```
169+
You can put a "*" in any directory (e.g. config) you don't want to filter by, although that won't work if adding the line of the testcase.
170+
171+
> [!NOTE]
172+
> We specify the `--no-start` flag to stop *ExUnit* from starting the application, to reduce resource consumption.
173+
124174
### Docker
125175

126176
The repo includes a `Dockerfile` for the consensus client. It can be built with:
@@ -135,7 +185,7 @@ Then you run it with `docker run`, adding CLI flags as needed:
135185
docker run consensus --checkpoint-sync <url> --network <network> ...
136186
```
137187

138-
# Testing Environment with Kurtosis
188+
## Testing Environment with Kurtosis
139189

140190
To test the node locally, we can simulate other nodes and start from genesis using [`Kurtosis`](https://docs.kurtosis.com/) and the Lambda Class fork of [`ethereum-package`](https://github.com/lambdaclass/ethereum-package.git).
141191

@@ -280,55 +330,114 @@ make kurtosis.clean
280330
make kurtosis.purge
281331
```
282332

283-
## Consensus spec tests
333+
## Live Metrics
284334

285-
You can run all of them with:
335+
When running the node, metrics are available at [`http://localhost:9568/metrics`](http://localhost:9568/metrics) in Prometheus format.
336+
337+
### Grafana
338+
339+
A docker-compose is available at [`metrics/`](./metrics) with a Grafana-Prometheus setup preloaded with dashboards that disponibilize the data.
340+
To run it, install [Docker Compose](https://docs.docker.com/compose/) and execute:
286341

287342
```shell
288-
make spec-test
343+
make grafana-up
289344
```
290345

291-
Or only run those of a specific config with:
346+
After that, open [`http://localhost:3000/`](http://localhost:3000/) in a browser.
347+
The default username and password are both `admin`.
292348

293-
```shell
294-
make spec-test-config-`config`
349+
To stop the containers run `make grafana-down`. For cleaning up the metrics data, run `make grafana-clean`.
295350

296-
# Some examples
297-
make spec-test-config-mainnet
298-
make spec-test-config-minimal
299-
make spec-test-config-general
351+
## Benchmarks
352+
353+
Several benchmarks are provided in the `/bench` directory. They are all standard elixir scripts, so they can be run as such. For example:
354+
355+
```bash
356+
mix run bench/byte_reversal.exs
300357
```
301358

302-
Or by a single runner in all configs, with:
359+
Some of the benchmarks require a state or blocks to be available in the db. For this, the easiest thing is to run `make checkpoint-sync` so an anchor state and block are downloaded for mainnet, and optimistic sync starts. If the benchmark requires additional blocks, maybe wait until the first chunk is downloaded and block processing is executed at least once.
303360

304-
```shell
305-
make spec-test-runner-`runner`
361+
Some need to be executed with `--mode db` in order to not have the store replaced by the application. This needs to be added at the end, like so:
306362

307-
# Some examples
308-
make spec-test-runner-ssz_static
309-
make spec-test-runner-bls
310-
make spec-test-runner-operations
363+
```bash
364+
mix run <script> --mode db
311365
```
312366

313-
The complete list of test runners can be found [here](https://github.com/ethereum/consensus-specs/tree/dev/tests/formats).
367+
A quick summary of the available benchmarks:
314368

315-
If you want to specify both a config and a runner:
369+
- `deposit_tree`: measures the time of saving and loading an the "execution chain" state, mainly to test how much it costs to save and load a realistic deposit tree. Uses benchee. The conclusion was very low (the order of μs).
370+
- `byte_reversal`: compares three different methods for byte reversal as a bitlist/bitvector operation. This concludes that using numbers as internal representation for those types would be the most efficient. If we ever need to improve them, that would be a good starting point.
371+
- `shuffling_bench`: compares different methods for shuffling: shuffling a list in one go vs computing each shuffle one by one. Shuffling the full list was proved to be 10x faster.
372+
- `block_processing`: builds a fork choice store with an anchor block and state. Uses the next block available to apply `on_block`, `on_attestation` and `on_attester_slashing` handlers. Runs these handlers 30 times. To run this, at least 2 blocks and a state must be available in the db. It also needs you to set the slot manually at the beginning of an epoch. Try it for the slot that appeared when you ran checkpoint sync (you'll see in the logs something along the lines of `[Checkpoint sync] Received beacon state and block slot=9597856`)
373+
- `multiple_block_processing`: _currently under revision_. Similar to block processing but with a range of slots so state transition is performed multiple times. The main advantage is that by performing more than one state transition it helps test caches and have a more average-case measurement.
374+
- `SSZ benchmarks`: they compare between our own library and the rust nif ssz library. To run any of these two benchmarks you previously need to have a BeaconState in the database.
375+
- `encode_decode_bench`: compares the libraries at encoding and decoding a Checkpoint and a BeaconState container.
376+
- `hash_tree_root_bench`: compares the libraries at performing the hash tree root of a Beacon State and packed list of numbers.
377+
378+
## Profiling
379+
380+
### QCachegrind
381+
382+
To install [QCachegrind](https://github.com/KDE/kcachegrind) via [Homebrew](https://formulae.brew.sh/formula/qcachegrind), run:
383+
384+
```sh
385+
brew install qcachegrind
386+
```
387+
388+
To build a qcachegrind profile, run, inside iex:
389+
390+
```elixir
391+
LambdaEthereumConsensus.Profile.build()
392+
```
393+
394+
Options and details are in the `Profile` package. After the profile trace is generated, you open it in qcachegrind with:
316395

317396
```shell
318-
make spec-test-mainnet-operations
319-
make spec-test-minimal-epoch_processing
320-
make spec-test-general-bls
397+
qcachegrind callgrind.out.<trace_name>
321398
```
322399

323-
More advanced filtering (e.g. by fork or handler) will be re-added again, but if you want to only run a specific test, you can always do that manually with:
400+
If you want to group the traces by function instead of process, you can use the following before viewing it in qcachegrind:
324401

325402
```shell
326-
mix test --no-start test/generated/<config>/<fork>/<runner>.exs:<line_of_your_testcase>
403+
grep -v "^ob=" callgrind.out.trace_name > callgrind.out.merged.trace_name
327404
```
328-
You can put a "*" in any directory (e.g. config) you don't want to filter by, although that won't work if adding the line of the testcase.
329405

330-
> [!NOTE]
331-
> We specify the `--no-start` flag to stop *ExUnit* from starting the application, to reduce resource consumption.
406+
### etop
407+
408+
Another useful tool to quickly diagnose processes taking too much CPU is `:etop`, similar to UNIX `top` command. This is installed by default in erlang, and included in the `:observer` extra application in `mix.exs`. You can run it with:
409+
410+
```elixir
411+
:etop.start()
412+
```
413+
414+
In particular, the `reds` metric symbolizes `reductions`, which can roughly be interpreted as the number of calls a function got.
415+
This can be used to identify infinite loops or busy waits.
416+
417+
Also of note is the `:sort` option, that allows sorting the list by, for example, message queue size:
418+
419+
```elixir
420+
:etop.start(sort: :msg_q)
421+
```
422+
423+
_Note: If you want to use the `:observer` GUI and not just `etop`, you'll probably need `:wx` also set in your extra applications, there is an easy way to do this, just set the `EXTRA_APPLICATIONS` environment variable to `WX` (`export EXTRA_APPLICATIONS=WX`) before starting the node_
424+
425+
### eFlambè
426+
427+
When optimizing code, it might be useful to have a graphic way to determine bottlenecks in the system.
428+
In that case, you can use [eFlambè](https://github.com/Stratus3D/eflambe) to generate flamegraphs of specific functions.
429+
The following code will capture information from 10 calls to `Handlers.on_block/2`, dumping it in different files named \<timestamp\>-eflambe-output.bggg.
430+
431+
```elixir
432+
:eflambe.capture({LambdaEthereumConsensus.ForkChoice.Handlers, :on_block, 2}, 10)
433+
```
434+
435+
The files generated can be processed via common flamegraph tools.
436+
For example, using [Brendan Gregg's stack](https://github.com/brendangregg/FlameGraph):
437+
438+
```shell
439+
cat *-eflambe-output.bggg | flamegraph.pl - > flamegraph.svg
440+
```
332441

333442
## Why Elixir?
334443

@@ -467,88 +576,6 @@ Lambda Ethereum Consensus is more than just a project; it's a community-driven i
467576

468577
**Thank you for being a part of our journey. Let's build an amazing future for Ethereum together! 🚀🌍**
469578

470-
## Metrics
471-
472-
When running the node, metrics are available at [`http://localhost:9568/metrics`](http://localhost:9568/metrics) in Prometheus format.
473-
474-
### Grafana
475-
476-
A docker-compose is available at [`metrics/`](./metrics) with a Grafana-Prometheus setup preloaded with dashboards that disponibilize the data.
477-
To run it, install [Docker Compose](https://docs.docker.com/compose/) and execute:
478-
479-
```shell
480-
make grafana-up
481-
```
482-
483-
After that, open [`http://localhost:3000/`](http://localhost:3000/) in a browser.
484-
The default username and password are both `admin`.
485-
486-
To stop the containers run `make grafana-down`. For cleaning up the metrics data, run `make grafana-clean`.
487-
488-
## Profiling
489-
490-
### QCachegrind
491-
492-
To install [QCachegrind](https://github.com/KDE/kcachegrind) via [Homebrew](https://formulae.brew.sh/formula/qcachegrind), run:
493-
494-
```sh
495-
brew install qcachegrind
496-
```
497-
498-
To build a qcachegrind profile, run, inside iex:
499-
500-
```elixir
501-
LambdaEthereumConsensus.Profile.build()
502-
```
503-
504-
Options and details are in the `Profile` package. After the profile trace is generated, you open it in qcachegrind with:
505-
506-
```shell
507-
qcachegrind callgrind.out.<trace_name>
508-
```
509-
510-
If you want to group the traces by function instead of process, you can use the following before viewing it in qcachegrind:
511-
512-
```shell
513-
grep -v "^ob=" callgrind.out.trace_name > callgrind.out.merged.trace_name
514-
```
515-
516-
### etop
517-
518-
Another useful tool to quickly diagnose processes taking too much CPU is `:etop`, similar to UNIX `top` command. This is installed by default in erlang, and included in the `:observer` extra application in `mix.exs`. You can run it with:
519-
520-
```elixir
521-
:etop.start()
522-
```
523-
524-
In particular, the `reds` metric symbolizes `reductions`, which can roughly be interpreted as the number of calls a function got.
525-
This can be used to identify infinite loops or busy waits.
526-
527-
Also of note is the `:sort` option, that allows sorting the list by, for example, message queue size:
528-
529-
```elixir
530-
:etop.start(sort: :msg_q)
531-
```
532-
533-
_Note: If you want to use the `:observer` GUI and not just `etop`, you'll probably need `:wx` also set in your extra applications, there is an easy way to do this, just set the `EXTRA_APPLICATIONS` environment variable to `WX` (`export EXTRA_APPLICATIONS=WX`) before starting the node_
534-
535-
### eFlambè
536-
537-
When optimizing code, it might be useful to have a graphic way to determine bottlenecks in the system.
538-
In that case, you can use [eFlambè](https://github.com/Stratus3D/eflambe) to generate flamegraphs of specific functions.
539-
The following code will capture information from 10 calls to `Handlers.on_block/2`, dumping it in different files named \<timestamp\>-eflambe-output.bggg.
540-
541-
```elixir
542-
:eflambe.capture({LambdaEthereumConsensus.ForkChoice.Handlers, :has_block?, 2}, 10)
543-
```
544-
545-
The files generated can be processed via common flamegraph tools.
546-
For example, using [Brendan Gregg's stack](https://github.com/brendangregg/FlameGraph):
547-
548-
```shell
549-
cat *-eflambe-output.bggg | flamegraph.pl - > flamegraph.svg
550-
```
551-
552579
## Code of Conduct
553580

554581
### Our Pledge

bench/block_processing.exs

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,54 @@ alias LambdaEthereumConsensus.ForkChoice
22
alias LambdaEthereumConsensus.ForkChoice.Handlers
33
alias LambdaEthereumConsensus.StateTransition.Cache
44
alias LambdaEthereumConsensus.Store
5+
alias LambdaEthereumConsensus.Store.BlockBySlot
56
alias LambdaEthereumConsensus.Store.BlockDb
67
alias LambdaEthereumConsensus.Store.StateDb
78
alias Types.BeaconState
89
alias Types.BlockInfo
910
alias Types.SignedBeaconBlock
11+
alias Types.StateInfo
1012

1113
Logger.configure(level: :warning)
12-
13-
{:ok, _} = Store.Db.start_link([])
14-
{:ok, _} = Store.Blocks.start_link([])
15-
{:ok, _} = Store.BlockStates.start_link([])
1614
Cache.initialize_cache()
1715

1816
# NOTE: this slot must be at the beginning of an epoch (i.e. a multiple of 32)
19-
slot = 4_213_280
17+
slot = 9_591_424
2018

2119
IO.puts("fetching blocks...")
22-
{:ok, %BeaconState{} = state} = StateDb.get_state_by_slot(slot)
20+
{:ok, %StateInfo{beacon_state: state}} = StateDb.get_state_by_slot(slot)
2321
{:ok, %BlockInfo{signed_block: block}} = BlockDb.get_block_info_by_slot(slot)
24-
{:ok, %BlockInfo{signed_block: new_block}} = BlockDb.get_block_info_by_slot(slot + 1)
22+
{:ok, %BlockInfo{signed_block: new_block} = block_info} = BlockDb.get_block_info_by_slot(slot + 1)
2523

2624
IO.puts("initializing store...")
2725
{:ok, store} = Types.Store.get_forkchoice_store(state, block)
2826
store = Handlers.on_tick(store, store.time + 30)
2927

30-
attestations = new_block.message.body.attestations
31-
attester_slashings = new_block.message.body.attester_slashings
32-
33-
{:ok, root} = BlockDb.get_block_root_by_slot(slot)
28+
{:ok, root} = BlockBySlot.get(slot)
3429

3530
IO.puts("about to process block: #{slot + 1}, with root: #{Base.encode16(root)}...")
3631
IO.puts("#{length(attestations)} attestations ; #{length(attester_slashings)} attester slashings")
3732
IO.puts("")
3833

39-
on_block = fn ->
40-
# process block attestations
41-
{:ok, new_store} = Handlers.on_block(store, new_block)
42-
43-
{:ok, new_store} =
44-
attestations
45-
|> ForkChoice.apply_handler(new_store, &Handlers.on_attestation(&1, &2, true))
46-
47-
# process block attester slashings
48-
{:ok, _} =
49-
attester_slashings
50-
|> ForkChoice.apply_handler(new_store, &Handlers.on_attester_slashing/2)
34+
if System.get_env("FLAMA") do
35+
Flama.run({ForkChoice, :process_block, [block_info, store]})
36+
else
37+
Benchee.run(
38+
%{
39+
"block (full cache)" => fn ->
40+
ForkChoice.process_block(block_info, store)
41+
end
42+
},
43+
time: 30
44+
)
45+
46+
Benchee.run(
47+
%{
48+
"block (empty cache)" => fn _ ->
49+
ForkChoice.process_block(block_info, store)
50+
end
51+
},
52+
time: 30,
53+
before_each: fn _ -> Cache.clear_cache() end
54+
)
5155
end
52-
53-
Benchee.run(
54-
%{
55-
"block (full cache)" => fn -> on_block.() end
56-
},
57-
time: 30
58-
)
59-
60-
Benchee.run(
61-
%{
62-
"block (empty cache)" => fn _ -> on_block.() end
63-
},
64-
time: 30,
65-
before_each: fn _ -> Cache.clear_cache() end
66-
)

0 commit comments

Comments
 (0)