From 2da6d9179ceba9d2c2d9373005c1c8df8ce036c0 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 5 Feb 2025 16:31:43 -0300 Subject: [PATCH 1/5] initial test --- .../fork_choice/head.ex | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/lib/lambda_ethereum_consensus/fork_choice/head.ex b/lib/lambda_ethereum_consensus/fork_choice/head.ex index 9114bc0e8..78ca97ac9 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/head.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/head.ex @@ -8,30 +8,41 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do alias Types.BeaconState alias Types.Store + require Logger + @spec get_head(Store.t()) :: {:ok, Types.root()} | {:error, any} def get_head(%Store{} = store) do - # Get filtered block tree that only includes viable branches - blocks = get_filtered_block_tree(store) - # Execute the LMD-GHOST fork choice - head = store.justified_checkpoint.root - + # Get the justified state once {_store, %BeaconState{} = justified_state} = Store.get_checkpoint_state(store, store.justified_checkpoint) - # PERF: return just the parent root and the block root in `get_filtered_block_tree` - Stream.cycle([nil]) - |> Enum.reduce_while(head, fn nil, head -> - blocks - |> Stream.filter(fn {_, block} -> block.parent_root == head end) - |> Stream.map(fn {root, _} -> root end) - # Ties broken by favoring block with lexicographically higher root - |> Enum.sort(:desc) - |> then(fn - [] -> {:halt, head} - c -> {:cont, Enum.max_by(c, &get_weight(store, &1, justified_state))} - end) - end) - |> then(&{:ok, &1}) + filtered_blocks = get_filtered_block_tree(store) + + head = store.justified_checkpoint.root + start_time = System.monotonic_time(:millisecond) + head = compute_head(store, filtered_blocks, head, justified_state) + Logger.info("Head computation took: #{(System.monotonic_time(:millisecond) - start_time) /1000} s") + {:ok, head} + end + + defp compute_head(store, blocks, current_root, justified_state) do + children = for {parent_root, root} <- blocks, parent_root == current_root, do: root + + case children do + [] -> + current_root + + [only_child] -> + # Directly continue without a max_by call + compute_head(store, blocks, only_child, justified_state) + + candidates -> + # Choose the candidate with the maximal weight according to get_weight/3 + start_time = System.monotonic_time(:millisecond) + best_child = Enum.max_by(candidates, &get_weight(store, &1, justified_state)) + Logger.info("Choosing best child took: #{(System.monotonic_time(:millisecond) - start_time) /1000} s") + compute_head(store, blocks, best_child, justified_state) + end end defp get_weight(%Store{} = store, root, state) do @@ -83,7 +94,7 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do base = store.justified_checkpoint.root block = Blocks.get_block!(base) {_, blocks} = filter_block_tree(store, base, block, %{}) - blocks + blocks |> Enum.map(fn {root, block} -> {root, block.parent_root} end) end defp filter_block_tree(%Store{} = store, block_root, block, blocks) do From f38ab6108da4257bd5cc1006c4db4e8565e20d0b Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 5 Feb 2025 16:47:24 -0300 Subject: [PATCH 2/5] remove uove unneded diffs and format --- .../fork_choice/head.ex | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/lambda_ethereum_consensus/fork_choice/head.ex b/lib/lambda_ethereum_consensus/fork_choice/head.ex index 78ca97ac9..88c8b3e91 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/head.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/head.ex @@ -12,16 +12,21 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do @spec get_head(Store.t()) :: {:ok, Types.root()} | {:error, any} def get_head(%Store{} = store) do - # Get the justified state once + # Get filtered block tree that only includes viable branches + filtered_blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + {_store, %BeaconState{} = justified_state} = Store.get_checkpoint_state(store, store.justified_checkpoint) - filtered_blocks = get_filtered_block_tree(store) - - head = store.justified_checkpoint.root start_time = System.monotonic_time(:millisecond) head = compute_head(store, filtered_blocks, head, justified_state) - Logger.info("Head computation took: #{(System.monotonic_time(:millisecond) - start_time) /1000} s") + + Logger.info( + "Head computation took: #{(System.monotonic_time(:millisecond) - start_time) / 1000} s" + ) + {:ok, head} end @@ -40,7 +45,11 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do # Choose the candidate with the maximal weight according to get_weight/3 start_time = System.monotonic_time(:millisecond) best_child = Enum.max_by(candidates, &get_weight(store, &1, justified_state)) - Logger.info("Choosing best child took: #{(System.monotonic_time(:millisecond) - start_time) /1000} s") + + Logger.info( + "Choosing best child took: #{(System.monotonic_time(:millisecond) - start_time) / 1000} s" + ) + compute_head(store, blocks, best_child, justified_state) end end From 25dd37eca4e6a9e17ee8382e8ea249dfd0fa0dff Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 5 Feb 2025 17:02:51 -0300 Subject: [PATCH 3/5] Fixed an issue and readded sort to maintain the previous logic --- lib/lambda_ethereum_consensus/fork_choice/head.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/lambda_ethereum_consensus/fork_choice/head.ex b/lib/lambda_ethereum_consensus/fork_choice/head.ex index 88c8b3e91..c7ed26418 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/head.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/head.ex @@ -21,6 +21,7 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do Store.get_checkpoint_state(store, store.justified_checkpoint) start_time = System.monotonic_time(:millisecond) + head = compute_head(store, filtered_blocks, head, justified_state) Logger.info( @@ -31,7 +32,7 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do end defp compute_head(store, blocks, current_root, justified_state) do - children = for {parent_root, root} <- blocks, parent_root == current_root, do: root + children = for {root, parent_root} <- blocks, parent_root == current_root, do: root case children do [] -> @@ -44,7 +45,12 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do candidates -> # Choose the candidate with the maximal weight according to get_weight/3 start_time = System.monotonic_time(:millisecond) - best_child = Enum.max_by(candidates, &get_weight(store, &1, justified_state)) + + best_child = + candidates + # Ties broken by favoring block with lexicographically higher root + |> Enum.sort(:desc) + |> Enum.max_by(&get_weight(store, &1, justified_state)) Logger.info( "Choosing best child took: #{(System.monotonic_time(:millisecond) - start_time) / 1000} s" From ad9999176f92a6271ac0d5e13552600fed803735 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 5 Feb 2025 17:18:12 -0300 Subject: [PATCH 4/5] remove testing time and added a comment --- .../fork_choice/head.ex | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/lambda_ethereum_consensus/fork_choice/head.ex b/lib/lambda_ethereum_consensus/fork_choice/head.ex index c7ed26418..fd9184709 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/head.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/head.ex @@ -20,14 +20,7 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do {_store, %BeaconState{} = justified_state} = Store.get_checkpoint_state(store, store.justified_checkpoint) - start_time = System.monotonic_time(:millisecond) - head = compute_head(store, filtered_blocks, head, justified_state) - - Logger.info( - "Head computation took: #{(System.monotonic_time(:millisecond) - start_time) / 1000} s" - ) - {:ok, head} end @@ -44,18 +37,12 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do candidates -> # Choose the candidate with the maximal weight according to get_weight/3 - start_time = System.monotonic_time(:millisecond) - best_child = candidates # Ties broken by favoring block with lexicographically higher root |> Enum.sort(:desc) |> Enum.max_by(&get_weight(store, &1, justified_state)) - Logger.info( - "Choosing best child took: #{(System.monotonic_time(:millisecond) - start_time) / 1000} s" - ) - compute_head(store, blocks, best_child, justified_state) end end @@ -105,11 +92,12 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do # Retrieve a filtered block tree from ``store``, only returning branches # whose leaf state's justified/finalized info agrees with that in ``store``. + # Only return the roots and their parent roots. defp get_filtered_block_tree(%Store{} = store) do base = store.justified_checkpoint.root block = Blocks.get_block!(base) {_, blocks} = filter_block_tree(store, base, block, %{}) - blocks |> Enum.map(fn {root, block} -> {root, block.parent_root} end) + Enum.map(blocks, fn {root, block} -> {root, block.parent_root} end) end defp filter_block_tree(%Store{} = store, block_root, block, blocks) do From cb109582ad0514ceb33824835ba7a579190699b4 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Wed, 5 Feb 2025 17:26:39 -0300 Subject: [PATCH 5/5] remove unneded require --- lib/lambda_ethereum_consensus/fork_choice/head.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/lambda_ethereum_consensus/fork_choice/head.ex b/lib/lambda_ethereum_consensus/fork_choice/head.ex index fd9184709..0d14d1a27 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/head.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/head.ex @@ -8,8 +8,6 @@ defmodule LambdaEthereumConsensus.ForkChoice.Head do alias Types.BeaconState alias Types.Store - require Logger - @spec get_head(Store.t()) :: {:ok, Types.root()} | {:error, any} def get_head(%Store{} = store) do # Get filtered block tree that only includes viable branches