Skip to content

Commit 07eaaa3

Browse files
authored
fix: recompute head was taking more than 20s in mainnet (#1380)
1 parent 55e55cb commit 07eaaa3

File tree

1 file changed

+28
-16
lines changed
  • lib/lambda_ethereum_consensus/fork_choice

1 file changed

+28
-16
lines changed

lib/lambda_ethereum_consensus/fork_choice/head.ex

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,38 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do
1111
@spec get_head(Store.t()) :: {:ok, Types.root()} | {:error, any}
1212
def get_head(%Store{} = store) do
1313
# Get filtered block tree that only includes viable branches
14-
blocks = get_filtered_block_tree(store)
14+
filtered_blocks = get_filtered_block_tree(store)
1515
# Execute the LMD-GHOST fork choice
1616
head = store.justified_checkpoint.root
1717

1818
{_store, %BeaconState{} = justified_state} =
1919
Store.get_checkpoint_state(store, store.justified_checkpoint)
2020

21-
# PERF: return just the parent root and the block root in `get_filtered_block_tree`
22-
Stream.cycle([nil])
23-
|> Enum.reduce_while(head, fn nil, head ->
24-
blocks
25-
|> Stream.filter(fn {_, block} -> block.parent_root == head end)
26-
|> Stream.map(fn {root, _} -> root end)
27-
# Ties broken by favoring block with lexicographically higher root
28-
|> Enum.sort(:desc)
29-
|> then(fn
30-
[] -> {:halt, head}
31-
c -> {:cont, Enum.max_by(c, &get_weight(store, &1, justified_state))}
32-
end)
33-
end)
34-
|> then(&{:ok, &1})
21+
head = compute_head(store, filtered_blocks, head, justified_state)
22+
{:ok, head}
23+
end
24+
25+
defp compute_head(store, blocks, current_root, justified_state) do
26+
children = for {root, parent_root} <- blocks, parent_root == current_root, do: root
27+
28+
case children do
29+
[] ->
30+
current_root
31+
32+
[only_child] ->
33+
# Directly continue without a max_by call
34+
compute_head(store, blocks, only_child, justified_state)
35+
36+
candidates ->
37+
# Choose the candidate with the maximal weight according to get_weight/3
38+
best_child =
39+
candidates
40+
# Ties broken by favoring block with lexicographically higher root
41+
|> Enum.sort(:desc)
42+
|> Enum.max_by(&get_weight(store, &1, justified_state))
43+
44+
compute_head(store, blocks, best_child, justified_state)
45+
end
3546
end
3647

3748
defp get_weight(%Store{} = store, root, state) do
@@ -79,11 +90,12 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do
7990

8091
# Retrieve a filtered block tree from ``store``, only returning branches
8192
# whose leaf state's justified/finalized info agrees with that in ``store``.
93+
# Only return the roots and their parent roots.
8294
defp get_filtered_block_tree(%Store{} = store) do
8395
base = store.justified_checkpoint.root
8496
block = Blocks.get_block!(base)
8597
{_, blocks} = filter_block_tree(store, base, block, %{})
86-
blocks
98+
Enum.map(blocks, fn {root, block} -> {root, block.parent_root} end)
8799
end
88100

89101
defp filter_block_tree(%Store{} = store, block_root, block, blocks) do

0 commit comments

Comments
 (0)