From e17070bebb8d2975152d577fc3f61555e25144a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Mon, 15 Jul 2024 18:00:18 +0200 Subject: [PATCH 1/9] refactor: make BlockBySlot its own KvSchema (#1217) --- .../controllers/v1/beacon_controller.ex | 4 +- lib/beacon_api/helpers.ex | 11 ++- .../store/block_by_slot.ex | 65 +++++++++++++ .../store/block_db.ex | 95 ++++++------------- .../store/checkpoint_states_db.ex | 2 +- .../store/kv_schema.ex | 49 +++++++--- lib/types/types.ex | 8 ++ test/fixtures/block.ex | 6 ++ test/fixtures/utils.ex | 3 + test/unit/store/block_by_slot_test.exs | 60 ++++++++++++ test/unit/store/block_db_test.exs | 92 ++++++++++++++++++ test/unit/store/kv_schema_test.exs | 17 +++- 12 files changed, 323 insertions(+), 89 deletions(-) create mode 100644 lib/lambda_ethereum_consensus/store/block_by_slot.ex create mode 100644 test/unit/store/block_by_slot_test.exs create mode 100644 test/unit/store/block_db_test.exs diff --git a/lib/beacon_api/controllers/v1/beacon_controller.ex b/lib/beacon_api/controllers/v1/beacon_controller.ex index f01258399..1f33036bf 100644 --- a/lib/beacon_api/controllers/v1/beacon_controller.ex +++ b/lib/beacon_api/controllers/v1/beacon_controller.ex @@ -5,7 +5,7 @@ defmodule BeaconApi.V1.BeaconController do alias BeaconApi.ErrorController alias BeaconApi.Helpers alias BeaconApi.Utils - alias LambdaEthereumConsensus.Store.BlockDb + alias LambdaEthereumConsensus.Store.BlockBySlot alias LambdaEthereumConsensus.Store.Blocks alias LambdaEthereumConsensus.Store.StoreDb @@ -94,7 +94,7 @@ defmodule BeaconApi.V1.BeaconController do def get_block_root(conn, %{block_id: block_id}) do with {slot, ""} when slot >= 0 <- Integer.parse(block_id), - {:ok, block_root} <- BlockDb.get_block_root_by_slot(slot) do + {:ok, block_root} <- BlockBySlot.get(slot) do conn |> root_response(block_root, true, false) else :not_found -> diff --git a/lib/beacon_api/helpers.ex b/lib/beacon_api/helpers.ex index 20b95772e..fc5511bc5 100644 --- a/lib/beacon_api/helpers.ex +++ b/lib/beacon_api/helpers.ex @@ -4,13 +4,15 @@ defmodule BeaconApi.Helpers do """ alias LambdaEthereumConsensus.ForkChoice + alias LambdaEthereumConsensus.Store.BlockBySlot alias LambdaEthereumConsensus.Store.BlockDb alias LambdaEthereumConsensus.Store.Blocks alias LambdaEthereumConsensus.Store.StateDb - alias Types.StateInfo - alias Types.BeaconState alias Types.SignedBeaconBlock + alias Types.StateInfo + + import Types.Guards @type named_root() :: :genesis | :justified | :finalized | :head @type block_id() :: named_root() | :invalid_id | Types.slot() | Types.root() @@ -78,11 +80,14 @@ defmodule BeaconApi.Helpers do def block_root_by_block_id(slot) when is_integer(slot) do with :ok <- check_valid_slot(slot, ForkChoice.get_current_chain_slot()), - {:ok, root} <- BlockDb.get_block_root_by_slot(slot) do + {:ok, root} when is_root(root) <- BlockBySlot.get(slot) do # TODO compute is_optimistic_or_invalid() and is_finalized() execution_optimistic = true finalized = false {:ok, {root, execution_optimistic, finalized}} + else + {:ok, :empty_slot} -> :empty_slot + other -> other end end diff --git a/lib/lambda_ethereum_consensus/store/block_by_slot.ex b/lib/lambda_ethereum_consensus/store/block_by_slot.ex new file mode 100644 index 000000000..765a180c7 --- /dev/null +++ b/lib/lambda_ethereum_consensus/store/block_by_slot.ex @@ -0,0 +1,65 @@ +defmodule LambdaEthereumConsensus.Store.BlockBySlot do + @moduledoc """ + KvSchema that stores block roots indexed by slot. As we store blocks by their root, this module + acts as an index if we need to look for them using their root. Some use cases are block pruning + (removing all blocks prior to a slot) or checking if a range of slots contain blocks, for + checkpoint sync checks. + """ + + alias LambdaEthereumConsensus.Store.KvSchema + use KvSchema, prefix: "blockSlot" + @type value_t :: Types.root() | :empty_slot + + ################################ + ### PUBLIC API + ################################ + + @doc """ + Checks if all the blocks between first_slot and last_slot are present in the db. + This iterates through the db checking each one individually, although it only checks + the keys, so it doesn't need to decode the values, making it a relatively cheap + linear O(last_slot - first_slot) operation. + """ + @spec all_present?(Types.slot(), Types.slot()) :: boolean() + def all_present?(first_slot, last_slot) do + fold_keys(last_slot, MapSet.new(), fn slot, set -> MapSet.put(set, slot) end, + include_first: true + ) + |> case do + {:ok, available} -> + Enum.all?(first_slot..last_slot, fn slot -> slot in available end) + + {:error, :invalid_iterator} -> + false + + {:error, "Failed to start iterator for table" <> _} -> + false + end + end + + ################################ + ### Schema implementation + ################################ + + @impl KvSchema + @spec encode_key(Types.slot()) :: {:ok, binary()} | {:error, binary()} + def encode_key(slot), do: {:ok, <>} + + @impl KvSchema + @spec decode_key(binary()) :: {:ok, integer()} | {:error, binary()} + def decode_key(<>), do: {:ok, slot} + + def decode_key(other) do + {:error, "[Block by slot] Could not decode slot, not 64 bit integer: #{other}"} + end + + @impl KvSchema + @spec encode_value(value_t()) :: {:ok, value_t()} | {:error, binary()} + def encode_value(:empty_slot), do: {:ok, <<>>} + def encode_value(<<_::256>> = root), do: {:ok, root} + + @impl KvSchema + @spec decode_value(value_t()) :: {:ok, value_t()} | {:error, binary()} + def decode_value(<<>>), do: {:ok, :empty_slot} + def decode_value(<<_::256>> = root), do: {:ok, root} +end diff --git a/lib/lambda_ethereum_consensus/store/block_db.ex b/lib/lambda_ethereum_consensus/store/block_db.ex index da170cf5d..a498188cd 100644 --- a/lib/lambda_ethereum_consensus/store/block_db.ex +++ b/lib/lambda_ethereum_consensus/store/block_db.ex @@ -3,12 +3,12 @@ defmodule LambdaEthereumConsensus.Store.BlockDb do Storage and retrieval of blocks. """ require Logger + alias LambdaEthereumConsensus.Store.BlockBySlot alias LambdaEthereumConsensus.Store.Db alias LambdaEthereumConsensus.Store.Utils alias Types.BlockInfo @block_prefix "blockHash" - @blockslot_prefix "blockSlot" @block_status_prefix "blockStatus" @spec store_block_info(BlockInfo.t()) :: :ok @@ -22,8 +22,7 @@ defmodule LambdaEthereumConsensus.Store.BlockDb do # TODO: this should apply fork-choice if not applied elsewhere # TODO: handle cases where slot is empty if not is_nil(block_info.signed_block) do - slothash_key = block_root_by_slot_key(block_info.signed_block.message.slot) - Db.put(slothash_key, block_info.root) + BlockBySlot.put(block_info.signed_block.message.slot, block_info.root) end end @@ -35,24 +34,15 @@ defmodule LambdaEthereumConsensus.Store.BlockDb do end end - @spec get_block_root_by_slot(Types.slot()) :: - {:ok, Types.root()} | {:error, String.t()} | :not_found | :empty_slot - def get_block_root_by_slot(slot) do - key = block_root_by_slot_key(slot) - block = Db.get(key) - - case block do - {:ok, <<>>} -> :empty_slot - _ -> block - end - end - @spec get_block_info_by_slot(Types.slot()) :: {:ok, BlockInfo.t()} | {:error, String.t()} | :not_found | :empty_slot def get_block_info_by_slot(slot) do # WARN: this will return the latest block received for the given slot - with {:ok, root} <- get_block_root_by_slot(slot) do - get_block_info(root) + # TODO: Are we actually saving empty slots in this index? + case BlockBySlot.get(slot) do + {:ok, :empty_slot} -> :empty_slot + {:ok, root} -> get_block_info(root) + other -> other end end @@ -95,58 +85,33 @@ defmodule LambdaEthereumConsensus.Store.BlockDb do @spec prune_blocks_older_than(non_neg_integer()) :: :ok | {:error, String.t()} | :not_found def prune_blocks_older_than(slot) do Logger.info("[BlockDb] Pruning started.", slot: slot) - initial_key = slot |> block_root_by_slot_key() - - slots_to_remove = - Stream.resource( - fn -> init_keycursor(initial_key) end, - &next_slot(&1, :prev), - &close_cursor/1 - ) - |> Enum.to_list() - - slots_to_remove |> Enum.each(&remove_block_by_slot/1) - Logger.info("[BlockDb] Pruning finished. #{Enum.count(slots_to_remove)} blocks removed.") - end - - @spec remove_block_by_slot(non_neg_integer()) :: :ok | :not_found - defp remove_block_by_slot(slot) do - slothash_key = block_root_by_slot_key(slot) - - with {:ok, block_root} <- Db.get(slothash_key) do - key_block = block_key(block_root) - Db.delete(slothash_key) - Db.delete(key_block) - end - end - - defp init_keycursor(initial_key) do - with {:ok, it} <- Db.iterate_keys(), - {:ok, _key} <- Exleveldb.iterator_move(it, initial_key) do - it - else - # DB is empty - {:error, :invalid_iterator} -> nil - end - end - defp next_slot(nil, _movement), do: {:halt, nil} - - defp next_slot(it, movement) do - case Exleveldb.iterator_move(it, movement) do - {:ok, @blockslot_prefix <> <>} -> - {[key], it} - - _ -> - {:halt, it} + result = + BlockBySlot.fold_keys(slot, 0, fn slot, acc -> + case BlockBySlot.get(slot) do + {:ok, :empty_slot} -> + BlockBySlot.delete(slot) + acc + 1 + + {:ok, block_root} -> + BlockBySlot.delete(slot) + Db.delete(block_key(block_root)) + acc + 1 + + other -> + Logger.error( + "[Block pruning] Failed to remove block from slot #{inspect(slot)}. Reason: #{inspect(other)}" + ) + end + end) + + # TODO: the separate get operation is avoided if we implement folding with values in KvSchema. + case result do + {:ok, n_removed} -> Logger.info("[BlockDb] Pruning finished. #{n_removed} blocks removed.") + {:error, reason} -> Logger.error("[BlockDb] Error pruning blocks: #{inspect(reason)}") end end - defp close_cursor(nil), do: :ok - defp close_cursor(it), do: :ok = Exleveldb.iterator_close(it) - defp block_key(root), do: Utils.get_key(@block_prefix, root) - defp block_root_by_slot_key(slot), do: Utils.get_key(@blockslot_prefix, slot) - defp block_status_key(status), do: Utils.get_key(@block_status_prefix, Atom.to_string(status)) end diff --git a/lib/lambda_ethereum_consensus/store/checkpoint_states_db.ex b/lib/lambda_ethereum_consensus/store/checkpoint_states_db.ex index 9548556de..3ad78baab 100644 --- a/lib/lambda_ethereum_consensus/store/checkpoint_states_db.ex +++ b/lib/lambda_ethereum_consensus/store/checkpoint_states_db.ex @@ -76,7 +76,7 @@ defmodule LambdaEthereumConsensus.Store.CheckpointStates do def prune(finalized_checkpoint) do Logger.debug("Pruning old checkpoint states") - case fold(finalized_checkpoint, 0, fn key, acc -> + case fold_keys(finalized_checkpoint, 0, fn key, acc -> delete(key) acc + 1 end) do diff --git a/lib/lambda_ethereum_consensus/store/kv_schema.ex b/lib/lambda_ethereum_consensus/store/kv_schema.ex index 9d5fe3592..299947fec 100644 --- a/lib/lambda_ethereum_consensus/store/kv_schema.ex +++ b/lib/lambda_ethereum_consensus/store/kv_schema.ex @@ -56,18 +56,21 @@ defmodule LambdaEthereumConsensus.Store.KvSchema do end) end - @spec fold(key(), acc(), (key(), acc() -> acc())) :: {:ok, acc()} | {:error, binary()} - def fold(start_key, starting_value, f) do - db_span("fold", fn -> - with {:ok, it} <- Db.iterate(), + @spec fold_keys(key(), acc(), (key(), acc() -> acc())) :: {:ok, acc()} | {:error, any()} + def fold_keys(start_key, starting_value, f, opts \\ []) do + db_span("fold_keys", fn -> + include_first? = Keyword.get(opts, :include_first, false) + direction = Keyword.get(opts, :direction, :prev) + + with {:ok, it} <- Db.iterate_keys(), {:ok, encoded_start} <- do_encode_key(start_key), - {:ok, ^encoded_start, _} <- Exleveldb.iterator_move(it, encoded_start) do - res = iterate(it, starting_value, f) + {:ok, ^encoded_start} <- Exleveldb.iterator_move(it, encoded_start) do + res = iterate(it, starting_value, f, direction, encoded_start, include_first?) Exleveldb.iterator_close(it) {:ok, res} else - # Failed at moving the iterator for the first time. - {:ok, some_key, _some_value} -> + # The iterator moved for the first time to a place where it wasn't expected. + {:ok, some_key} -> {:error, "Failed to start iterator for table #{@prefix}. The obtained key is: #{some_key}"} @@ -77,16 +80,32 @@ defmodule LambdaEthereumConsensus.Store.KvSchema do end) end - defp iterate(it, acc, f) do - case Exleveldb.iterator_move(it, :prev) do - # TODO: add option to get the value in the function too if needed. - {:ok, @prefix <> _ = k, v} -> - # TODO: plan for weird corner cases where the key can't be decoded. + defp iterate(it, acc, f, direction, _first_key, false) do + iterate(it, acc, f, direction) + end + + defp iterate(it, acc, f, direction, first_key, true) do + case accumulate(it, acc, f, first_key) do + {:cont, new_acc} -> iterate(it, new_acc, f, direction) + {:halt, new_acc} -> new_acc + end + end + + defp iterate(it, acc, f, direction) do + case accumulate(it, acc, f, direction) do + {:cont, acc} -> iterate(it, acc, f, direction) + {:halt, acc} -> acc + end + end + + defp accumulate(it, acc, f, direction) do + case Exleveldb.iterator_move(it, direction) do + {:ok, @prefix <> _ = k} -> {:ok, decoded_key} = do_decode_key(k) - iterate(it, f.(decoded_key, acc), f) + {:cont, f.(decoded_key, acc)} _ -> - acc + {:halt, acc} end end diff --git a/lib/types/types.ex b/lib/types/types.ex index dc7df1cf2..0d8ee00dd 100644 --- a/lib/types/types.ex +++ b/lib/types/types.ex @@ -48,4 +48,12 @@ defmodule Types do @type kzg_proof :: Kzg.proof() @type bls_signature :: Bls.signature() @type bls_pubkey :: Bls.pubkey() + + defmodule Guards do + @moduledoc """ + Module defining guards for some types. Added as needed. + """ + + defguard is_root(binary) when is_binary(binary) and byte_size(binary) == 32 + end end diff --git a/test/fixtures/block.ex b/test/fixtures/block.ex index d8fbc0c16..097e6f2bc 100644 --- a/test/fixtures/block.ex +++ b/test/fixtures/block.ex @@ -5,6 +5,7 @@ defmodule Fixtures.Block do alias Fixtures.Random alias LambdaEthereumConsensus.Utils.BitVector + alias Types.BlockInfo alias Types.BeaconBlock alias Types.BeaconBlockBody @@ -13,6 +14,11 @@ defmodule Fixtures.Block do alias Types.ExecutionPayloadHeader alias Types.SignedBeaconBlock + @spec block_info :: BlockInfo.t() + def block_info() do + signed_beacon_block() |> BlockInfo.from_block() + end + @spec signed_beacon_block :: SignedBeaconBlock.t() def signed_beacon_block() do %SignedBeaconBlock{ diff --git a/test/fixtures/utils.ex b/test/fixtures/utils.ex index a98d17bab..ffbf91f38 100644 --- a/test/fixtures/utils.ex +++ b/test/fixtures/utils.ex @@ -18,6 +18,9 @@ defmodule Fixtures.Random do binary(32) end + @spec slot() :: Types.slot() + def slot(), do: uint64() + @spec bls_signature :: binary def bls_signature() do binary(96) diff --git a/test/unit/store/block_by_slot_test.exs b/test/unit/store/block_by_slot_test.exs new file mode 100644 index 000000000..1f9a94e12 --- /dev/null +++ b/test/unit/store/block_by_slot_test.exs @@ -0,0 +1,60 @@ +defmodule Unit.Store.BlockBySlotTest do + alias Fixtures.Random + alias LambdaEthereumConsensus.Store.BlockBySlot + + use ExUnit.Case + + setup %{tmp_dir: tmp_dir} do + start_link_supervised!({LambdaEthereumConsensus.Store.Db, dir: tmp_dir}) + :ok + end + + @tag :tmp_dir + test "Basic saving a block root" do + root = Random.root() + slot = Random.slot() + assert :ok == BlockBySlot.put(slot, root) + assert {:ok, root} == BlockBySlot.get(slot) + end + + @tag :tmp_dir + test "all_present? should return true when checking on a subset or the full set, but false for elements outside" do + Enum.each(2..4, fn slot -> + root = Random.root() + assert :ok == BlockBySlot.put(slot, root) + end) + + assert BlockBySlot.all_present?(2, 4) + assert BlockBySlot.all_present?(3, 3) + refute BlockBySlot.all_present?(1, 4) + refute BlockBySlot.all_present?(2, 5) + refute BlockBySlot.all_present?(1, 1) + end + + @tag :tmp_dir + test "all_present? should return false when elements are missing in between" do + root = Random.root() + BlockBySlot.put(1, root) + BlockBySlot.put(3, root) + + assert BlockBySlot.all_present?(3, 3) + assert BlockBySlot.all_present?(1, 1) + refute BlockBySlot.all_present?(1, 3) + end + + @tag :tmp_dir + test "retrieving an empty slot" do + assert :ok == BlockBySlot.put(1, :empty_slot) + assert {:ok, :empty_slot} == BlockBySlot.get(1) + end + + @tag :tmp_dir + test "Trying to save an atom that's not :empty_slot fails" do + assert_raise(FunctionClauseError, fn -> BlockBySlot.put(1, :some_atom) end) + end + + @tag :tmp_dir + test "Trying to save a non-root binary fails" do + assert_raise(FunctionClauseError, fn -> BlockBySlot.put(1, "Hello") end) + end +end diff --git a/test/unit/store/block_db_test.exs b/test/unit/store/block_db_test.exs new file mode 100644 index 000000000..d1709b719 --- /dev/null +++ b/test/unit/store/block_db_test.exs @@ -0,0 +1,92 @@ +defmodule Unit.Store.BlockDbTest do + alias Fixtures.Block + alias LambdaEthereumConsensus.Store.BlockBySlot + alias LambdaEthereumConsensus.Store.BlockDb + + use ExUnit.Case + + setup %{tmp_dir: tmp_dir} do + Application.fetch_env!(:lambda_ethereum_consensus, ChainSpec) + |> Keyword.put(:config, MainnetConfig) + |> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1)) + + start_link_supervised!({LambdaEthereumConsensus.Store.Db, dir: tmp_dir}) + :ok + end + + @tag :tmp_dir + test "Simple block saving and loading" do + block_info = Block.block_info() + BlockDb.store_block_info(block_info) + assert {:ok, block_info} == BlockDb.get_block_info(block_info.root) + end + + @tag :tmp_dir + test "A saved block's root can be retrieved using its slot" do + block_info = Block.block_info() + BlockDb.store_block_info(block_info) + + assert {:ok, block_info} == + BlockDb.get_block_info_by_slot(block_info.signed_block.message.slot) + end + + @tag :tmp_dir + test "Pruning deletes only blocks prior to the one selected as target" do + blocks = + [block_1, block_2, block_3] = + [Block.block_info(), Block.block_info(), Block.block_info()] + |> Enum.sort_by(& &1.signed_block.message.slot) + + Enum.each(blocks, &BlockDb.store_block_info/1) + + assert {:ok, block_1} == BlockDb.get_block_info(block_1.root) + assert {:ok, block_2} == BlockDb.get_block_info(block_2.root) + assert {:ok, block_3} == BlockDb.get_block_info(block_3.root) + + BlockDb.prune_blocks_older_than(block_2.signed_block.message.slot) + + assert :not_found == BlockDb.get_block_info(block_1.root) + assert {:ok, block_2} == BlockDb.get_block_info(block_2.root) + assert {:ok, block_3} == BlockDb.get_block_info(block_3.root) + end + + @tag :tmp_dir + test "Pruning on a non existent root returns and doesn't delete anything" do + blocks = + [block_1, block_2, block_3] = + [Block.block_info(), Block.block_info(), Block.block_info()] + |> Enum.sort_by(& &1.signed_block.message.slot) + + Enum.each(blocks, &BlockDb.store_block_info/1) + + random_slot = (blocks |> Enum.map(& &1.signed_block.message.slot) |> Enum.max()) + 1 + assert :ok == BlockDb.prune_blocks_older_than(random_slot) + assert {:ok, block_1} == BlockDb.get_block_info(block_1.root) + assert {:ok, block_2} == BlockDb.get_block_info(block_2.root) + assert {:ok, block_3} == BlockDb.get_block_info(block_3.root) + end + + @tag :tmp_dir + test "Empty blocks don't affect pruning" do + blocks = + [block_1, block_2, block_3] = + [Block.block_info(), Block.block_info(), Block.block_info()] + |> Enum.sort_by(& &1.signed_block.message.slot) + + Enum.each(blocks, &BlockDb.store_block_info/1) + + block_slots = Enum.map(blocks, & &1.signed_block.message.slot) + + min_slot = Enum.min(block_slots) - 1 + max_slot = Enum.max(block_slots) + 1 + BlockBySlot.put(max_slot, :empty_slot) + BlockBySlot.put(min_slot, :empty_slot) + + assert :ok == BlockDb.prune_blocks_older_than(max_slot) + assert :not_found == BlockDb.get_block_info(block_1.root) + assert :not_found == BlockDb.get_block_info(block_2.root) + assert :not_found == BlockDb.get_block_info(block_3.root) + assert {:ok, :empty_slot} == BlockBySlot.get(max_slot) + assert :not_found == BlockBySlot.get(min_slot) + end +end diff --git a/test/unit/store/kv_schema_test.exs b/test/unit/store/kv_schema_test.exs index c97b626a5..03c860d72 100644 --- a/test/unit/store/kv_schema_test.exs +++ b/test/unit/store/kv_schema_test.exs @@ -97,7 +97,18 @@ defmodule Unit.Store.KvSchemaTest do NumberSchema.put(5, %{b: 3}) NumberSchema.put(70, %{c: 5}) - assert {:ok, 6} == NumberSchema.fold(70, 0, fn n, acc -> acc + n end) + assert {:ok, 6} == NumberSchema.fold_keys(70, 0, fn n, acc -> acc + n end) + end + + @tag :tmp_dir + test "Folding includes the first value if so requested" do + TupleSchema.put({1, 2}, []) + NumberSchema.put(1, %{"a" => "b"}) + NumberSchema.put(5, %{b: 3}) + NumberSchema.put(70, %{c: 5}) + + assert {:ok, 76} == + NumberSchema.fold_keys(70, 0, fn n, acc -> acc + n end, include_first: true) end @tag :tmp_dir @@ -106,7 +117,7 @@ defmodule Unit.Store.KvSchemaTest do NumberSchema.put(200, %{b: 3}) NumberSchema.put(700, %{c: 5}) - assert {:ok, 300} == NumberSchema.fold(700, 0, fn n, acc -> acc + n end) + assert {:ok, 300} == NumberSchema.fold_keys(700, 0, fn n, acc -> acc + n end) end @tag :tmp_dir @@ -114,6 +125,6 @@ defmodule Unit.Store.KvSchemaTest do NumberSchema.put(1, %{"a" => "b"}) NumberSchema.put(5, %{b: 3}) - {:error, _} = NumberSchema.fold(3, 0, fn n, acc -> acc + n end) + {:error, _} = NumberSchema.fold_keys(3, 0, fn n, acc -> acc + n end) end end From e417f74540ab5088209e845ec32efc48079986de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Avila=20Gast=C3=B3n?= <72628438+avilagaston9@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:00:38 -0300 Subject: [PATCH 2/9] feat: add blocks node graph (#1215) --- .../beacon/pending_blocks.ex | 33 ++++++- .../fork_choice/fork_choice.ex | 5 +- lib/lambda_ethereum_consensus/metrics.ex | 58 +++++++++++-- lib/lambda_ethereum_consensus/store/blocks.ex | 27 +++++- lib/lambda_ethereum_consensus/telemetry.ex | 5 +- .../grafana/provisioning/dashboards/home.json | 85 ++++++++++++++++++- 6 files changed, 202 insertions(+), 11 deletions(-) diff --git a/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex b/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex index fa0b8eaa7..cf3d423b0 100644 --- a/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex +++ b/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex @@ -7,6 +7,10 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do require Logger alias LambdaEthereumConsensus.ForkChoice + alias LambdaEthereumConsensus.Libp2pPort + alias LambdaEthereumConsensus.P2P.BlockDownloader + + alias LambdaEthereumConsensus.Metrics alias LambdaEthereumConsensus.P2P.BlobDownloader alias LambdaEthereumConsensus.Store.BlobDb alias LambdaEthereumConsensus.Store.Blocks @@ -19,6 +23,8 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do | {nil, :invalid | :download} @type state :: nil + @download_retries 100 + @doc """ If the block is not present, it will be stored as pending. @@ -44,7 +50,7 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do Blocks.new_block_info(block_info) process_block_and_check_children(block_info) else - BlobDownloader.request_blobs_by_root(missing_blobs, &process_blobs/1, 30) + BlobDownloader.request_blobs_by_root(missing_blobs, &process_blobs/1, @download_retries) block_info |> BlockInfo.change_status(:download_blobs) @@ -90,7 +96,22 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do case Blocks.get_block_info(parent_root) do nil -> + Logger.debug("[PendingBlocks] Add parent to download #{inspect(parent_root)}") Blocks.add_block_to_download(parent_root) + + BlockDownloader.request_blocks_by_root( + [parent_root], + fn result -> + process_downloaded_block(result) + end, + @download_retries + ) + + Metrics.block_relationship( + parent_root, + block_info.root + ) + :download_pending %BlockInfo{status: :invalid} -> @@ -118,6 +139,16 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do end end + defp process_downloaded_block({:ok, [block]}) do + Libp2pPort.add_block(block) + end + + defp process_downloaded_block({:error, reason}) do + Logger.error("Error downloading block: #{inspect(reason)}") + + # We might want to declare a block invalid here. + end + defp process_blobs({:ok, blobs}), do: add_blobs(blobs) defp process_blobs({:error, reason}) do diff --git a/lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex b/lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex index 2dc812e5e..c0869f1f3 100644 --- a/lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex +++ b/lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex @@ -9,6 +9,7 @@ defmodule LambdaEthereumConsensus.ForkChoice do alias LambdaEthereumConsensus.Execution.ExecutionChain alias LambdaEthereumConsensus.ForkChoice.Handlers alias LambdaEthereumConsensus.ForkChoice.Head + alias LambdaEthereumConsensus.Metrics alias LambdaEthereumConsensus.P2P.Gossip.OperationsCollector alias LambdaEthereumConsensus.StateTransition.Misc alias LambdaEthereumConsensus.Store.BlobDb @@ -27,7 +28,7 @@ defmodule LambdaEthereumConsensus.ForkChoice do ########################## @spec init_store(Store.t(), Types.uint64()) :: :ok | :error - def init_store(%Store{head_slot: head_slot} = store, time) do + def init_store(%Store{head_slot: head_slot, head_root: head_root} = store, time) do Logger.info("[Fork choice] Initialized store.", slot: head_slot) store = Handlers.on_tick(store, time) @@ -35,6 +36,8 @@ defmodule LambdaEthereumConsensus.ForkChoice do :telemetry.execute([:sync, :store], %{slot: Store.get_current_slot(store)}) :telemetry.execute([:sync, :on_block], %{slot: head_slot}) + Metrics.block_status(head_root, head_slot, :transitioned) + persist_store(store) end diff --git a/lib/lambda_ethereum_consensus/metrics.ex b/lib/lambda_ethereum_consensus/metrics.ex index 6d7748959..109e75f3a 100644 --- a/lib/lambda_ethereum_consensus/metrics.ex +++ b/lib/lambda_ethereum_consensus/metrics.ex @@ -2,6 +2,8 @@ defmodule LambdaEthereumConsensus.Metrics do @moduledoc """ Basic telemetry metric generation to be used across the node. """ + alias LambdaEthereumConsensus.Store.Blocks + require Logger def tracer({:add_peer, %{}}) do :telemetry.execute([:network, :pubsub_peers], %{}, %{result: "add"}) @@ -68,13 +70,59 @@ defmodule LambdaEthereumConsensus.Metrics do end end - def block_status(root, status) do - hex_root = root |> Base.encode16() + def block_status(root, slot, new_status) do + block_status_execute(root, new_status, slot, 1) + end - :telemetry.execute([:blocks, :status], %{}, %{ - mainstat: status, + @doc """ + - Sets the old status to '0' to deactivate it and sets the new status to '1' so that we can filter the Grafana table. + - If the old status is ':download', it will be deactivated with a 'nil' slot, since that's how it was activated. + """ + def block_status(root, slot, :download, new_status) do + block_status_execute(root, :download, nil, 0) + block_status_execute(root, new_status, slot, 1) + end + + def block_status(root, slot, old_status, new_status) do + block_status_execute(root, old_status, slot, 0) + block_status_execute(root, new_status, slot, 1) + end + + defp block_status_execute(root, status, slot, value) do + hex_root = Base.encode16(root) + + Logger.debug( + "[Metrics] slot = #{inspect(slot)}, status = #{inspect(status)}, value = #{inspect(value)}" + ) + + :telemetry.execute([:blocks, :status], %{total: value}, %{ id: hex_root, - title: hex_root + mainstat: status, + color: map_color(status), + title: slot, + detail__root: hex_root }) end + + def block_relationship(nil, _), do: :ok + + def block_relationship(parent_root, root) do + # If we try to add an edge to a non-existent node, it will crash. + if Blocks.get_block_info(parent_root) do + hex_parent_root = parent_root |> Base.encode16() + hex_root = root |> Base.encode16() + + :telemetry.execute([:blocks, :relationship], %{total: 1}, %{ + id: hex_root <> hex_parent_root, + source: hex_parent_root, + target: hex_root + }) + end + end + + defp map_color(:transitioned), do: "blue" + defp map_color(:pending), do: "green" + defp map_color(:download_blobs), do: "yellow" + defp map_color(:download), do: "orange" + defp map_color(:invalid), do: "red" end diff --git a/lib/lambda_ethereum_consensus/store/blocks.ex b/lib/lambda_ethereum_consensus/store/blocks.ex index 53f91cd02..a348410b9 100644 --- a/lib/lambda_ethereum_consensus/store/blocks.ex +++ b/lib/lambda_ethereum_consensus/store/blocks.ex @@ -82,6 +82,22 @@ defmodule LambdaEthereumConsensus.Store.Blocks do # list. If it's not in the list, the operation is equivalent to only adding it in the correct # one. BlockDb.change_root_status(block_info.root, :download, block_info.status) + + {slot, parent_root} = + if block_info.signed_block do + {block_info.signed_block.message.slot, block_info.signed_block.message.parent_root} + else + {nil, nil} + end + + Metrics.block_status( + block_info.root, + slot, + :download, + block_info.status + ) + + Metrics.block_relationship(parent_root, block_info.root) end @doc """ @@ -89,14 +105,21 @@ defmodule LambdaEthereumConsensus.Store.Blocks do """ @spec change_status(BlockInfo.t(), BlockInfo.block_status()) :: BlockInfo.t() def change_status(block_info, status) do - Metrics.block_status(block_info.root, status) - new_block_info = BlockInfo.change_status(block_info, status) store_block_info(new_block_info) old_status = block_info.status BlockDb.change_root_status(block_info.root, old_status, status) + Metrics.block_status( + block_info.root, + block_info.signed_block.message.slot, + old_status, + status + ) + + Metrics.block_relationship(block_info.signed_block.message.parent_root, block_info.root) + new_block_info end diff --git a/lib/lambda_ethereum_consensus/telemetry.ex b/lib/lambda_ethereum_consensus/telemetry.ex index 3f0bbec40..af38fa691 100644 --- a/lib/lambda_ethereum_consensus/telemetry.ex +++ b/lib/lambda_ethereum_consensus/telemetry.ex @@ -148,7 +148,10 @@ defmodule LambdaEthereumConsensus.Telemetry do last_value("fork_choice.recompute_head.exception.duration", unit: {:native, :millisecond} ), - counter("blocks.status.count", tags: [:title, :mainstat, :id]) + last_value("blocks.status.total", tags: [:id, :mainstat, :color, :title, :detail__root]), + last_value("blocks.relationship.total", + tags: [:id, :source, :target] + ) ] end diff --git a/metrics/grafana/provisioning/dashboards/home.json b/metrics/grafana/provisioning/dashboards/home.json index 2c9985225..b4156322f 100644 --- a/metrics/grafana/provisioning/dashboards/home.json +++ b/metrics/grafana/provisioning/dashboards/home.json @@ -28,6 +28,89 @@ "links": [], "liveNow": false, "panels": [ + { + "datasource": { + "uid": "PBFA97CFB590B2093", + "type": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 31, + "options": { + "nodes": {}, + "edges": {} + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "blocks_status_total", + "format": "table", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A", + "useBackend": false, + "exemplar": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "blocks_relationship_total", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "B", + "useBackend": false, + "exemplar": false + } + ], + "title": "Blockchain View", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "fieldName": "Value #A", + "config": { + "id": "equal", + "options": { + "value": 0 + } + } + } + ], + "type": "exclude", + "match": "any" + }, + "filter": { + "id": "byRefId", + "options": "A" + }, + "topic": "series" + } + ], + "type": "nodeGraph" + }, { "datasource": { "type": "prometheus", @@ -2698,4 +2781,4 @@ "uid": "90EXFQnIk", "version": 3, "weekStart": "" -} +} \ No newline at end of file From b15ab6d060f8176837301c18469e1cd0f92f9fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:02:12 -0300 Subject: [PATCH 3/9] chore(deps): bump rustler from 0.33.0 to 0.34.0 in /native/bls_nif (#1225) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- native/bls_nif/Cargo.lock | 20 ++++++++++++++------ native/bls_nif/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/native/bls_nif/Cargo.lock b/native/bls_nif/Cargo.lock index e4abc9237..b9c6e68e4 100644 --- a/native/bls_nif/Cargo.lock +++ b/native/bls_nif/Cargo.lock @@ -353,6 +353,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "itertools" version = "0.10.5" @@ -567,21 +573,23 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustler" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d51ae0239c57c3a3e603dd855ace6795078ef33c95c85d397a100ac62ed352" +checksum = "e94bdfa68c0388cbd725f1ca54e975956482c262599e5cced04a903eec918b7f" dependencies = [ + "inventory", "rustler_codegen", "rustler_sys", ] [[package]] name = "rustler_codegen" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27061f1a2150ad64717dca73902678c124b0619b0d06563294df265bc84759e1" +checksum = "996dc019acb78b91b4e0c1bd6fa2cd509a835d309de762dc15213b97eac399da" dependencies = [ "heck", + "inventory", "proc-macro2", "quote", "syn 2.0.58", @@ -589,9 +597,9 @@ dependencies = [ [[package]] name = "rustler_sys" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2062df0445156ae93cf695ef38c00683848d956b30507592143c01fe8fb52fda" +checksum = "3914a75a147934353c3772a77b774c79fdf80ba84e8347f52a50df0c164aaff2" dependencies = [ "regex", "unreachable", diff --git a/native/bls_nif/Cargo.toml b/native/bls_nif/Cargo.toml index 905b398b5..0bbdcb8bc 100644 --- a/native/bls_nif/Cargo.toml +++ b/native/bls_nif/Cargo.toml @@ -10,5 +10,5 @@ path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] -rustler = "0.33.0" +rustler = "0.34.0" bls = { git = "https://github.com/sigp/lighthouse", package = "bls", rev = "v5.2.1" } From 1f1cd52ae0dd239f87439fa6c6f99bdff78a9bdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:03:03 -0300 Subject: [PATCH 4/9] chore(deps): bump open_api_spex from 3.19.1 to 3.20.0 (#1224) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 7f3acbf34..bdf7cdf9d 100644 --- a/mix.lock +++ b/mix.lock @@ -33,11 +33,11 @@ "logfmt_ex": {:hex, :logfmt_ex, "0.4.2", "e337b6072bd21ad61d8bbe38d9c591b5a8e4869ceba4967699d027baedf2eec8", [:mix], [], "hexpm", "7fad3704383d4595adf0da873e72c8b393120e67b1257f9102da881fde9d4249"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, - "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"}, + "open_api_spex": {:hex, :open_api_spex, "3.20.0", "d4fcf1ee297aa94a673cddb92734eb0bc7cac698be93949a223a50f724e3af89", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "2e9beea71142ff09f8f935579b39406e2c6b5a3978e7235978d7faf2f90cd081"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "patch": {:hex, :patch, "0.13.1", "2da5b508e4d6558924a0959d95dc3aa8176b5ccf2539e4567481448d61853ccc", [:mix], [], "hexpm", "75f805827d9db0c335155fbb857e6eeb5c85034c9dc668d146bc0bfe48fac822"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, From 5e4cbaecb785f9acfb8fa48049d38c80e52c6ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:03:53 -0300 Subject: [PATCH 5/9] chore(deps): bump tesla from 1.11.1 to 1.11.2 (#1223) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index bdf7cdf9d..8039ef985 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,7 @@ "aja": {:hex, :aja, "0.6.5", "e780fce3de247d86bb25097726021c5c53ebe383300290e26c61e4d36bfe85e8", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "cd377ac20f487dd7987be13772c37eb2f42ab038c0654714a1ecb3607f9e8590"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, + "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, @@ -71,7 +71,7 @@ "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, - "tesla": {:hex, :tesla, "1.11.1", "902ec0cd9fb06ba534be765f0eb78acd9d0ef70118230dc3a73fdc9afc91d036", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c02d7dd149633c55c40adfaad6c3ce2615cfc89258b67a7f428c14bb835c398c"}, + "tesla": {:hex, :tesla, "1.11.2", "24707ac48b52f72f88fc05d242b1c59a85d1ee6f16f19c312d7d3419665c9cd5", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c549cd03aec6a7196a641689dd378b799e635eb393f689b4bd756f750c7a4014"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, From 6576b2be88da4aa2bae8f7d9d11a33e3c1de8611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:04:51 -0300 Subject: [PATCH 6/9] chore(deps): bump recase from 0.8.0 to 0.8.1 (#1204) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8039ef985..abc0d26e4 100644 --- a/mix.lock +++ b/mix.lock @@ -53,7 +53,7 @@ "protobuf": {:hex, :protobuf, "0.12.0", "58c0dfea5f929b96b5aa54ec02b7130688f09d2de5ddc521d696eec2a015b223", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "75fa6cbf262062073dd51be44dd0ab940500e18386a6c4e87d5819a58964dc45"}, "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "recase": {:hex, :recase, "0.8.0", "ec9500abee5d493d41e3cbfd7d51a4e10957a164570be0c805d5c6661b8cdbae", [:mix], [], "hexpm", "0d4b67b81e7897af77552bd1e6d6148717a4b45ec5c7b014a48b0ba9a28946b5"}, + "recase": {:hex, :recase, "0.8.1", "ab98cd35857a86fa5ca99036f575241d71d77d9c2ab0c39aacf1c9b61f6f7d1d", [:mix], [], "hexpm", "9fd8d63e7e43bd9ea385b12364e305778b2bbd92537e95c4b2e26fc507d5e4c2"}, "recode": {:hex, :recode, "0.7.2", "aa24873b6eb4c90e635ad1f7e12b8e21575a087698bd6bda6e72a82c1298eca1", [:mix], [{:escape, "~> 0.1", [hex: :escape, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}], "hexpm", "d70fc60aae3c42781ec845515c1ddd4fe55218ed3fd8fe52267d338044ec7fb8"}, "redbug": {:hex, :redbug, "1.2.2", "366d8961770ddc7bb5d209fbadddfa7271005487f938c087a0e385a57abfee33", [:rebar3], [], "hexpm", "b5fe7b94e487be559cb0ec1c0e938c9761205d3e91a96bf263bdf1beaebea729"}, "rewrite": {:hex, :rewrite, "0.10.1", "238073297d122dad6b5501d761cb3bc0ce5bb4ab86e34c826c395f5f44b2f562", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "91f8d6fe363033e8ff60097bb5e0b76867667df0b4d67e79c2850444c02d8b19"}, From 8fbaa9e71bc13098c16499a3ad21d453401de117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:40:37 -0300 Subject: [PATCH 7/9] chore(deps): bump rustler from 0.33.0 to 0.34.0 (#1222) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index abc0d26e4..a90958805 100644 --- a/mix.lock +++ b/mix.lock @@ -23,9 +23,11 @@ "exleveldb": {:hex, :exleveldb, "0.14.0", "8e9353bbce38482d6971d254c6b98ceb50f3f179c94732b5d17db1be426fca18", [:mix], [{:eleveldb, "~> 2.2.20", [hex: :eleveldb, repo: "hexpm", optional: false]}], "hexpm", "803cd3b4c826a1e17e7e28f6afe224837a743b475e1a48336f186af3dd8636ad"}, "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "glob_ex": {:hex, :glob_ex, "0.1.6", "3a311ade50f6b71d638af660edcc844c3ab4eb2a2c816cfebb73a1d521bb2f9d", [:mix], [], "hexpm", "fda1e90e10f6029bd72967fef0c9891d0d14da89ca7163076e6028bfcb2c42fa"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"}, @@ -35,8 +37,10 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "open_api_spex": {:hex, :open_api_spex, "3.20.0", "d4fcf1ee297aa94a673cddb92734eb0bc7cac698be93949a223a50f724e3af89", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "2e9beea71142ff09f8f935579b39406e2c6b5a3978e7235978d7faf2f90cd081"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "patch": {:hex, :patch, "0.13.1", "2da5b508e4d6558924a0959d95dc3aa8176b5ccf2539e4567481448d61853ccc", [:mix], [], "hexpm", "75f805827d9db0c335155fbb857e6eeb5c85034c9dc668d146bc0bfe48fac822"}, @@ -56,9 +60,10 @@ "recase": {:hex, :recase, "0.8.1", "ab98cd35857a86fa5ca99036f575241d71d77d9c2ab0c39aacf1c9b61f6f7d1d", [:mix], [], "hexpm", "9fd8d63e7e43bd9ea385b12364e305778b2bbd92537e95c4b2e26fc507d5e4c2"}, "recode": {:hex, :recode, "0.7.2", "aa24873b6eb4c90e635ad1f7e12b8e21575a087698bd6bda6e72a82c1298eca1", [:mix], [{:escape, "~> 0.1", [hex: :escape, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}], "hexpm", "d70fc60aae3c42781ec845515c1ddd4fe55218ed3fd8fe52267d338044ec7fb8"}, "redbug": {:hex, :redbug, "1.2.2", "366d8961770ddc7bb5d209fbadddfa7271005487f938c087a0e385a57abfee33", [:rebar3], [], "hexpm", "b5fe7b94e487be559cb0ec1c0e938c9761205d3e91a96bf263bdf1beaebea729"}, + "req": {:hex, :req, "0.5.2", "70b4976e5fbefe84e5a57fd3eea49d4e9aa0ac015301275490eafeaec380f97f", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c63539ab4c2d6ced6114d2684276cef18ac185ee00674ee9af4b1febba1f986"}, "rewrite": {:hex, :rewrite, "0.10.1", "238073297d122dad6b5501d761cb3bc0ce5bb4ab86e34c826c395f5f44b2f562", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "91f8d6fe363033e8ff60097bb5e0b76867667df0b4d67e79c2850444c02d8b19"}, "rexbug": {:hex, :rexbug, "1.0.6", "024071c67d970151fbdc06f299faf8db3e1b2ac759a28623a9cc80a517fc74f2", [:mix], [{:mix_test_watch, ">= 0.5.0", [hex: :mix_test_watch, repo: "hexpm", optional: true]}, {:redbug, "~> 1.2", [hex: :redbug, repo: "hexpm", optional: false]}], "hexpm", "148ea724979413e9fd84ca3b4bb5d2d8b840ac481adfd645f5846fda409a642c"}, - "rustler": {:hex, :rustler, "0.33.0", "4a5b0a7a7b0b51549bea49947beff6fae9bc5d5326104dcd4531261e876b5619", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "7c4752728fee59a815ffd20c3429c55b644041f25129b29cdeb5c470b80ec5fd"}, + "rustler": {:hex, :rustler, "0.34.0", "e9a73ee419fc296a10e49b415a2eb87a88c9217aa0275ec9f383d37eed290c1c", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "1d0c7449482b459513003230c0e2422b0252245776fe6fd6e41cb2b11bd8e628"}, "scrypt_elixir": {:hex, :scrypt_elixir_copy, "0.1.1", "2b23573e8d9e6c93c8116cd17f9b453b6ebf0725b5317ecaeacaf73353a4dbd3", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "1eb5768b6b6c657770cbc00a9724f47bad4e9d664a2da3916030d591223561e7"}, "sentry": {:hex, :sentry, "10.6.2", "a867ab728d424e187ccb2bccc388170a740a79bc0ddccabd72d303b203acbe0e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "31bb84247274f9262fd300df0e3eb73302e4849cc6b7a6560bb2465f03fbd446"}, "snappyer": {:hex, :snappyer, "1.2.9", "9cc58470798648ce34c662ca0aa6daae31367667714c9a543384430a3586e5d3", [:rebar3], [], "hexpm", "18d00ca218ae613416e6eecafe1078db86342a66f86277bd45c95f05bf1c8b29"}, From 756cccab33505f8d9ddd6807c9effa9f39138e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Tue, 16 Jul 2024 18:31:44 +0200 Subject: [PATCH 8/9] feat: make checkpoint sync asynchronous (#1219) --- .../beacon/beacon_node.ex | 1 - .../beacon/pending_blocks.ex | 3 +- .../beacon/sync_blocks.ex | 137 ++++-------- .../p2p/block_downloader.ex | 9 +- .../p2p/gossip/beacon_block.ex | 2 + .../p2p/gossip/operations_collector.ex | 2 +- lib/libp2p_port.ex | 207 +++++++++++++++--- 7 files changed, 221 insertions(+), 140 deletions(-) diff --git a/lib/lambda_ethereum_consensus/beacon/beacon_node.ex b/lib/lambda_ethereum_consensus/beacon/beacon_node.ex index 79463f0c8..d6c4c6cd7 100644 --- a/lib/lambda_ethereum_consensus/beacon/beacon_node.ex +++ b/lib/lambda_ethereum_consensus/beacon/beacon_node.ex @@ -41,7 +41,6 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconNode do [ {LambdaEthereumConsensus.Beacon.Clock, {store.genesis_time, time}}, {LambdaEthereumConsensus.Libp2pPort, libp2p_args}, - LambdaEthereumConsensus.Beacon.SyncBlocks, {Task.Supervisor, name: PruneStatesSupervisor}, {Task.Supervisor, name: PruneBlocksSupervisor}, {Task.Supervisor, name: PruneBlobsSupervisor} diff --git a/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex b/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex index cf3d423b0..2dcf1e686 100644 --- a/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex +++ b/lib/lambda_ethereum_consensus/beacon/pending_blocks.ex @@ -7,7 +7,6 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do require Logger alias LambdaEthereumConsensus.ForkChoice - alias LambdaEthereumConsensus.Libp2pPort alias LambdaEthereumConsensus.P2P.BlockDownloader alias LambdaEthereumConsensus.Metrics @@ -140,7 +139,7 @@ defmodule LambdaEthereumConsensus.Beacon.PendingBlocks do end defp process_downloaded_block({:ok, [block]}) do - Libp2pPort.add_block(block) + add_block(block) end defp process_downloaded_block({:error, reason}) do diff --git a/lib/lambda_ethereum_consensus/beacon/sync_blocks.ex b/lib/lambda_ethereum_consensus/beacon/sync_blocks.ex index 750a7aea4..c4f22af1e 100644 --- a/lib/lambda_ethereum_consensus/beacon/sync_blocks.ex +++ b/lib/lambda_ethereum_consensus/beacon/sync_blocks.ex @@ -3,128 +3,69 @@ defmodule LambdaEthereumConsensus.Beacon.SyncBlocks do Performs an optimistic block sync from the finalized checkpoint to the current slot. """ - use Task - require Logger alias LambdaEthereumConsensus.ForkChoice alias LambdaEthereumConsensus.Libp2pPort alias LambdaEthereumConsensus.P2P.BlockDownloader - alias LambdaEthereumConsensus.P2P.Gossip alias LambdaEthereumConsensus.StateTransition.Misc - alias Types.SignedBeaconBlock @blocks_per_chunk 16 + @retries 50 - @type chunk :: %{from: Types.slot(), count: integer()} - - def start_link(opts) do - Task.start_link(__MODULE__, :run, [opts]) - end + @doc """ + Calculates how which blocks need to be downloaded to be up to date., and launches the download + requests. Returns the amount of blocks that need to be downloaded. - def run(_opts) do + If N blocks should be downloaded, N/16 range requests are performed. When each of those + finish, each block of those responses will be sent to libp2p port module individually using + Libp2pPort.add_block/1. + """ + @spec run() :: non_neg_integer() + def run() do # Initial sleep for faster app start - Process.sleep(1000) checkpoint = ForkChoice.get_finalized_checkpoint() initial_slot = Misc.compute_start_slot_at_epoch(checkpoint.epoch) + 1 last_slot = ForkChoice.get_current_chain_slot() # If we're around genesis, we consider ourselves synced - if last_slot > 0 do - perform_sync(initial_slot, last_slot) + if last_slot <= 0 do + Logger.info("[Optimistic sync] At genesis. No block sync will be needed.") + 0 else - start_subscriptions() - end - end - - @spec perform_sync(integer(), integer()) :: :ok - def perform_sync(initial_slot, last_slot) do - Enum.chunk_every(initial_slot..last_slot, @blocks_per_chunk) - |> Enum.map(fn chunk -> - first_slot = List.first(chunk) - last_slot = List.last(chunk) - count = last_slot - first_slot + 1 - %{from: first_slot, count: count} - end) - |> perform_sync() - end - - @spec perform_sync([chunk()]) :: :ok - def perform_sync(chunks) do - remaining = chunks |> Stream.map(fn %{count: c} -> c end) |> Enum.sum() - Logger.info("[Optimistic Sync] Blocks remaining: #{remaining}") - - results = - chunks - |> Task.async_stream( - fn chunk -> fetch_blocks_by_slot(chunk.from, chunk.count) end, - max_concurrency: 4, - timeout: 20_000, - on_timeout: :kill_task + Logger.info( + "[Optimistic sync] Performing optimistic sync between slots #{initial_slot} and #{last_slot}, for a total of #{last_slot - initial_slot + 1} slots." ) - |> Enum.map(fn - {:ok, result} -> result - {:error, error} -> {:error, error} - {:exit, :timeout} -> {:error, "timeout"} - end) - - results - |> Enum.flat_map(fn - {:ok, blocks} -> blocks - _other -> [] - end) - |> tap(fn blocks -> - Logger.info("[Optimistic Sync] Downloaded #{length(blocks)} blocks successfully.") - end) - |> Enum.each(&Libp2pPort.add_block/1) - - remaining_chunks = - Enum.zip(chunks, results) - |> Enum.flat_map(fn - {chunk, {:error, reason}} -> - if not String.contains?(inspect(reason), "failed to dial") do - Logger.debug( - "[Optimistic Sync] Failed downloading the chunk #{inspect(chunk)}. Reason: #{inspect(reason)}" - ) - end - [chunk] - - _other -> - [] + initial_slot..last_slot + |> Enum.chunk_every(@blocks_per_chunk) + |> Enum.map(fn chunk -> + first_slot = List.first(chunk) + last_slot = List.last(chunk) + count = last_slot - first_slot + 1 + + Logger.info( + "[Optimistic sync] Sending request for slots #{first_slot} to #{last_slot} (request size = #{count})." + ) + + BlockDownloader.request_blocks_by_range( + first_slot, + count, + &on_chunk_downloaded/1, + @retries + ) + + count end) - - if Enum.empty?(chunks) do - Logger.info("[Optimistic Sync] Sync completed") - start_subscriptions() - else - Process.sleep(1000) - perform_sync(remaining_chunks) + |> Enum.sum() end end - # TODO: handle subscription failures. - defp start_subscriptions() do - Gossip.BeaconBlock.subscribe_to_topic() - Gossip.BlobSideCar.subscribe_to_topics() - Gossip.OperationsCollector.subscribe_to_topics() + defp on_chunk_downloaded({:ok, range, blocks}) do + Libp2pPort.notify_blocks_downloaded(range, blocks) end - @spec fetch_blocks_by_slot(Types.slot(), non_neg_integer()) :: - {:ok, [SignedBeaconBlock.t()]} | {:error, String.t()} - def fetch_blocks_by_slot(from, count) do - case BlockDownloader.request_blocks_by_range_sync(from, count, 0) do - {:ok, blocks} -> - {:ok, blocks} - - {:error, error} -> - if not String.contains?(inspect(error), "failed to dial") do - Logger.debug( - "Blocks download failed for slot #{from} count #{count} Error: #{inspect(error)}" - ) - end - - {:error, error} - end + defp on_chunk_downloaded({:error, range, reason}) do + Libp2pPort.notify_block_download_failed(range, reason) end end diff --git a/lib/lambda_ethereum_consensus/p2p/block_downloader.ex b/lib/lambda_ethereum_consensus/p2p/block_downloader.ex index 702f7a84b..9278ee283 100644 --- a/lib/lambda_ethereum_consensus/p2p/block_downloader.ex +++ b/lib/lambda_ethereum_consensus/p2p/block_downloader.ex @@ -17,8 +17,10 @@ defmodule LambdaEthereumConsensus.P2P.BlockDownloader do # so we want to try again with a different peer @default_retries 5 + @type range :: {Types.slot(), Types.slot()} @type download_result :: {:ok, [SignedBeaconBlock.t()]} | {:error, any()} - @type on_blocks :: (download_result() -> term()) + @type on_blocks :: + ({:ok, range(), [SignedBeaconBlock.t()]} | {:error, range(), any()} -> term()) @doc """ Requests a series of blocks in batch, and synchronously (the caller will block waiting for the @@ -73,7 +75,7 @@ defmodule LambdaEthereumConsensus.P2P.BlockDownloader do :ok <- verify_batch(blocks, slot, count) do tags = %{result: "success", type: "by_slot", reason: "success"} :telemetry.execute([:network, :request], %{blocks: count}, tags) - on_blocks.({:ok, blocks}) + on_blocks.({:ok, {slot, slot + count - 1}, blocks}) else {:error, reason} -> tags = %{type: "by_slot", reason: parse_reason(reason)} @@ -85,7 +87,8 @@ defmodule LambdaEthereumConsensus.P2P.BlockDownloader do request_blocks_by_range(slot, count, on_blocks, retries - 1) else :telemetry.execute([:network, :request], %{blocks: 0}, Map.put(tags, :result, "error")) - on_blocks.({:error, reason}) + # TODO: Add block range that failed in the reason + on_blocks.({:error, {slot, slot + count - 1}, reason}) {:error, reason} end end diff --git a/lib/lambda_ethereum_consensus/p2p/gossip/beacon_block.ex b/lib/lambda_ethereum_consensus/p2p/gossip/beacon_block.ex index 69b2c037a..51eae17d9 100644 --- a/lib/lambda_ethereum_consensus/p2p/gossip/beacon_block.ex +++ b/lib/lambda_ethereum_consensus/p2p/gossip/beacon_block.ex @@ -57,6 +57,8 @@ defmodule LambdaEthereumConsensus.P2P.Gossip.BeaconBlock do "/eth2/#{fork_context}/beacon_block/ssz_snappy" end + def topics(), do: [topic()] + ########################## ### Private functions ########################## diff --git a/lib/lambda_ethereum_consensus/p2p/gossip/operations_collector.ex b/lib/lambda_ethereum_consensus/p2p/gossip/operations_collector.ex index af7f65474..46d0578e8 100644 --- a/lib/lambda_ethereum_consensus/p2p/gossip/operations_collector.ex +++ b/lib/lambda_ethereum_consensus/p2p/gossip/operations_collector.ex @@ -220,7 +220,7 @@ defmodule LambdaEthereumConsensus.P2P.Gossip.OperationsCollector do end end - defp topics() do + def topics() do fork_context = ForkChoice.get_fork_digest() |> Base.encode16(case: :lower) topics = diff --git a/lib/libp2p_port.ex b/lib/libp2p_port.ex index 149beb0ad..445b88796 100644 --- a/lib/libp2p_port.ex +++ b/lib/libp2p_port.ex @@ -10,6 +10,7 @@ defmodule LambdaEthereumConsensus.Libp2pPort do use GenServer alias LambdaEthereumConsensus.Beacon.PendingBlocks + alias LambdaEthereumConsensus.Beacon.SyncBlocks alias LambdaEthereumConsensus.ForkChoice alias LambdaEthereumConsensus.Metrics alias LambdaEthereumConsensus.P2P.Gossip.BeaconBlock @@ -20,9 +21,6 @@ defmodule LambdaEthereumConsensus.Libp2pPort do alias LambdaEthereumConsensus.P2p.Requests alias LambdaEthereumConsensus.StateTransition.Misc alias LambdaEthereumConsensus.Utils.BitVector - alias Types.EnrForkId - - alias LambdaEthereumConsensus.ForkChoice alias Libp2pProto.AddPeer alias Libp2pProto.Command alias Libp2pProto.Enr @@ -44,10 +42,15 @@ defmodule LambdaEthereumConsensus.Libp2pPort do alias Libp2pProto.SubscribeToTopic alias Libp2pProto.Tracer alias Libp2pProto.ValidateMessage + alias Types.EnrForkId require Logger - @port_name Application.app_dir(:lambda_ethereum_consensus, ["priv", "native", "libp2p_port"]) + @port_name Application.app_dir(:lambda_ethereum_consensus, [ + "priv", + "native", + "libp2p_port" + ]) @default_args [ listen_addr: [], @@ -74,6 +77,8 @@ defmodule LambdaEthereumConsensus.Libp2pPort do discovery_addresses: [String.t()] } + @sync_delay_millis 10_000 + ###################### ### API ###################### @@ -121,7 +126,10 @@ defmodule LambdaEthereumConsensus.Libp2pPort do # Sets libp2pport as the Req/Resp handler for the given protocol ID. @spec set_handler(String.t(), port()) :: boolean() defp set_handler(protocol_id, port) do - :telemetry.execute([:port, :message], %{}, %{function: "set_handler", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "set_handler", + direction: "elixir->" + }) c = {:set_handler, %SetHandler{protocol_id: protocol_id}} data = Command.encode(%Command{c: c}) @@ -136,7 +144,11 @@ defmodule LambdaEthereumConsensus.Libp2pPort do @spec add_peer(GenServer.server(), binary(), [String.t()], integer()) :: :ok | {:error, String.t()} def add_peer(pid \\ __MODULE__, id, addrs, ttl) do - :telemetry.execute([:port, :message], %{}, %{function: "add_peer", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "add_peer", + direction: "elixir->" + }) + c = %AddPeer{id: id, addrs: addrs, ttl: ttl} call_command(pid, {:add_peer, c}) end @@ -148,7 +160,11 @@ defmodule LambdaEthereumConsensus.Libp2pPort do @spec send_request(GenServer.server(), binary(), String.t(), binary()) :: {:ok, binary()} | {:error, String.t()} def send_request(pid \\ __MODULE__, peer_id, protocol_id, message) do - :telemetry.execute([:port, :message], %{}, %{function: "send_request", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "send_request", + direction: "elixir->" + }) + from = self() GenServer.cast( @@ -164,14 +180,21 @@ defmodule LambdaEthereumConsensus.Libp2pPort do Sends a request to a peer. The response will be processed by the Libp2p process. """ def send_async_request(pid \\ __MODULE__, peer_id, protocol_id, message, handler) do - :telemetry.execute([:port, :message], %{}, %{function: "send_request", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "send_request", + direction: "elixir->" + }) + GenServer.cast(pid, {:send_request, peer_id, protocol_id, message, handler}) end # Sends a response for the request with the given message ID. @spec send_response({String.t(), binary()}, port()) :: boolean() defp send_response({request_id, response}, port) do - :telemetry.execute([:port, :message], %{}, %{function: "send_response", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "send_response", + direction: "elixir->" + }) c = {:send_response, %SendResponse{request_id: request_id, message: response}} data = Command.encode(%Command{c: c}) @@ -198,7 +221,11 @@ defmodule LambdaEthereumConsensus.Libp2pPort do """ @spec publish(GenServer.server(), String.t(), binary()) :: :ok | {:error, String.t()} def publish(pid \\ __MODULE__, topic_name, message) do - :telemetry.execute([:port, :message], %{}, %{function: "publish", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "publish", + direction: "elixir->" + }) + call_command(pid, {:publish, %Publish{topic: topic_name, message: message}}) end @@ -206,14 +233,15 @@ defmodule LambdaEthereumConsensus.Libp2pPort do Subscribes to the given topic. After this, messages published to the topic will be received by `self()`. """ - @spec subscribe_to_topic(GenServer.server(), String.t(), module()) :: :ok | {:error, String.t()} + @spec subscribe_to_topic(GenServer.server(), String.t(), module()) :: + :ok | {:error, String.t()} def subscribe_to_topic(pid \\ __MODULE__, topic_name, module) do :telemetry.execute([:port, :message], %{}, %{ function: "subscribe_to_topic", direction: "elixir->" }) - GenServer.cast(pid, {:new_subscriptor, topic_name, module}) + GenServer.cast(pid, {:new_subscriber, topic_name, module}) call_command(pid, {:subscribe, %SubscribeToTopic{name: topic_name}}) end @@ -255,15 +283,23 @@ defmodule LambdaEthereumConsensus.Libp2pPort do direction: "elixir->" }) - cast_command(pid, {:validate_message, %ValidateMessage{msg_id: msg_id, result: result}}) + cast_command( + pid, + {:validate_message, %ValidateMessage{msg_id: msg_id, result: result}} + ) end @doc """ Updates the "eth2", "attnets", and "syncnets" ENR entries for the node. """ - @spec update_enr(GenServer.server(), Types.EnrForkId.t(), BitVector.t(), BitVector.t()) :: :ok + @spec update_enr(GenServer.server(), Types.EnrForkId.t(), BitVector.t(), BitVector.t()) :: + :ok def update_enr(pid \\ __MODULE__, enr_fork_id, attnets_bv, syncnets_bv) do - :telemetry.execute([:port, :message], %{}, %{function: "update_enr", direction: "elixir->"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "update_enr", + direction: "elixir->" + }) + # TODO: maybe move encoding to caller enr = encode_enr(enr_fork_id, attnets_bv, syncnets_bv) cast_command(pid, {:update_enr, enr}) @@ -295,7 +331,17 @@ defmodule LambdaEthereumConsensus.Libp2pPort do |> Enum.each(fn protocol_id -> set_handler(protocol_id, port) end) end - def add_block(pid \\ __MODULE__, block), do: GenServer.cast(pid, {:add_block, block}) + @doc """ + This function is only used by checkpoint sync to notify of new manually downloaded blocks + and it should not be related to other manual block downloads or gossip blocks. + """ + def notify_blocks_downloaded(pid \\ __MODULE__, range, blocks) do + GenServer.cast(pid, {:add_blocks, range, blocks}) + end + + def notify_block_download_failed(pid \\ __MODULE__, range, reason) do + GenServer.cast(pid, {:error_downloading_chunk, range, reason}) + end ######################## ### GenServer Callbacks @@ -319,19 +365,24 @@ defmodule LambdaEthereumConsensus.Libp2pPort do if enable_request_handlers, do: enable_request_handlers(port) Peerbook.init() + Process.send_after(self(), :sync_blocks, @sync_delay_millis) + + Logger.info( + "[Optimistic Sync] Waiting #{@sync_delay_millis / 1000} seconds to discover some peers before requesting blocks." + ) {:ok, %{ port: port, - subscriptors: %{}, - requests: Requests.new() + subscribers: %{}, + requests: Requests.new(), + syncing: true }} end @impl GenServer - def handle_cast({:new_subscriptor, topic, module}, %{subscriptors: subscriptors} = state) do - new_subscriptors = Map.put(subscriptors, topic, module) - {:noreply, %{state | subscriptors: new_subscriptors}} + def handle_cast({:new_subscriber, topic, module}, state) do + {:noreply, add_subscriber(state, topic, module)} end @impl GenServer @@ -370,11 +421,43 @@ defmodule LambdaEthereumConsensus.Libp2pPort do {:noreply, state |> Map.put(:requests, new_requests)} end - def handle_cast({:add_block, block}, state) do - PendingBlocks.add_block(block) + @impl GenServer + def handle_cast({:add_blocks, {first_slot, last_slot}, blocks}, state) do + n_blocks = length(blocks) + missing = last_slot - first_slot + 1 - n_blocks + + Logger.info( + "[Optimistic Sync] Range #{first_slot} - #{last_slot} downloaded successfully, with #{n_blocks} blocks and #{missing} missing." + ) + + Enum.each(blocks, &PendingBlocks.add_block/1) + new_state = Map.update!(state, :blocks_remaining, fn n -> n - n_blocks - missing end) + + if new_state.blocks_remaining > 0 do + Logger.info("[Optimistic Sync] Blocks remaining: #{new_state.blocks_remaining}") + {:noreply, new_state} + else + Logger.info("[Optimistic Sync] Sync completed. Subscribing to gossip topics.") + {:noreply, subscribe_to_gossip_topics(new_state)} + end + end + + @impl GenServer + def handle_cast({:error_downloading_chunk, range, reason}, state) do + Logger.error( + "[Optimistic Sync] Failed to download the block range #{inspect(range)}, no retries left. Reason: #{inspect(reason)}" + ) + + # TODO: kill the genserver or retry sync all together. {:noreply, state} end + @impl GenServer + def handle_info(:sync_blocks, state) do + blocks_to_download = SyncBlocks.run() + {:noreply, state |> Map.put(:blocks_remaining, blocks_to_download)} + end + @impl GenServer def handle_info({_port, {:data, data}}, state) do %Notification{n: {_, payload}} = Notification.decode(data) @@ -388,6 +471,7 @@ defmodule LambdaEthereumConsensus.Libp2pPort do @impl GenServer def handle_info(other, state) do :telemetry.execute([:port, :message], %{}, %{function: "other", direction: "->elixir"}) + Logger.error(inspect(other)) {:noreply, state} end @@ -396,10 +480,13 @@ defmodule LambdaEthereumConsensus.Libp2pPort do ### PRIVATE FUNCTIONS ###################### - defp handle_notification(%GossipSub{} = gs, %{subscriptors: subscriptors} = state) do - :telemetry.execute([:port, :message], %{}, %{function: "gossipsub", direction: "->elixir"}) + defp handle_notification(%GossipSub{} = gs, %{subscribers: subscribers} = state) do + :telemetry.execute([:port, :message], %{}, %{ + function: "gossipsub", + direction: "->elixir" + }) - case Map.fetch(subscriptors, gs.topic) do + case Map.fetch(subscribers, gs.topic) do {:ok, module} -> module.handle_gossip_message(gs.topic, gs.msg_id, gs.message) :error -> Logger.error("[Gossip] Received gossip from unknown topic: #{gs.topic}.") end @@ -415,7 +502,10 @@ defmodule LambdaEthereumConsensus.Libp2pPort do }, %{port: port} = state ) do - :telemetry.execute([:port, :message], %{}, %{function: "request", direction: "->elixir"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "request", + direction: "->elixir" + }) case IncomingRequestsHandler.handle(protocol_id, request_id, message) do {:ok, response} -> @@ -429,13 +519,21 @@ defmodule LambdaEthereumConsensus.Libp2pPort do end defp handle_notification(%NewPeer{peer_id: peer_id}, state) do - :telemetry.execute([:port, :message], %{}, %{function: "new peer", direction: "->elixir"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "new peer", + direction: "->elixir" + }) + Peerbook.handle_new_peer(peer_id) state end defp handle_notification(%Response{} = response, %{requests: requests} = state) do - :telemetry.execute([:port, :message], %{}, %{function: "response", direction: "->elixir"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "response", + direction: "->elixir" + }) + success = if response.success, do: :ok, else: :error {result, new_requests} = @@ -449,14 +547,22 @@ defmodule LambdaEthereumConsensus.Libp2pPort do end defp handle_notification(%Result{from: "", result: result}, state) do - :telemetry.execute([:port, :message], %{}, %{function: "result", direction: "->elixir"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "result", + direction: "->elixir" + }) + # TODO: amount of failures would be a useful metric _success_txt = if match?({:ok, _}, result), do: "success", else: "failed" state end defp handle_notification(%Result{from: from, result: result}, state) do - :telemetry.execute([:port, :message], %{}, %{function: "result", direction: "->elixir"}) + :telemetry.execute([:port, :message], %{}, %{ + function: "result", + direction: "->elixir" + }) + pid = :erlang.binary_to_term(from) send(pid, {:response, result}) state @@ -493,10 +599,17 @@ defmodule LambdaEthereumConsensus.Libp2pPort do defp receive_response() do receive do - {:response, {:node_identity, identity}} -> identity - {:response, {res, %ResultMessage{message: []}}} -> res - {:response, {res, %ResultMessage{message: message}}} -> [res | message] |> List.to_tuple() - {:response, {res, response}} -> {res, response} + {:response, {:node_identity, identity}} -> + identity + + {:response, {res, %ResultMessage{message: []}}} -> + res + + {:response, {res, %ResultMessage{message: message}}} -> + [res | message] |> List.to_tuple() + + {:response, {res, response}} -> + {res, response} end end @@ -526,4 +639,28 @@ defmodule LambdaEthereumConsensus.Libp2pPort do } |> encode_enr(attnets, syncnets) end + + defp add_subscriber(state, topic, module) do + update_in(state.subscribers, fn + subscribers -> Map.put(subscribers, topic, module) + end) + end + + defp topics_for_module(module) do + Enum.map(module.topics(), fn topic -> {module, topic} end) + end + + defp subscribe_to_gossip_topics(state) do + [ + LambdaEthereumConsensus.P2P.Gossip.BeaconBlock, + LambdaEthereumConsensus.P2P.Gossip.BlobSideCar, + LambdaEthereumConsensus.P2P.Gossip.OperationsCollector + ] + |> Enum.flat_map(&topics_for_module/1) + |> Enum.reduce(state, fn {module, topic}, state -> + command = %Command{c: {:subscribe, %SubscribeToTopic{name: topic}}} + send_data(state.port, Command.encode(command)) + add_subscriber(state, topic, module) + end) + end end From a9fb42d38a84d0bbf8f9b167a2e195cae48fefd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Tue, 16 Jul 2024 18:32:10 +0200 Subject: [PATCH 9/9] docs: add execution section in architecture document (#1203) --- docs/architecture.md | 170 +++++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 181752527..70e57a42f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,15 +2,12 @@ ## Processes summary -### Supervision tree - This is our complete supervision tree. ```mermaid graph LR Application[Application
<:one_for_one>] BeaconNode[BeaconNode
<:one_for_all>] -P2P.IncomingRequests[P2P.IncomingRequests
<:one_for_one>] ValidatorManager[ValidatorManager
<:one_for_one>] Telemetry[Telemetry
<:one_for_one>] @@ -18,28 +15,27 @@ Application --> Telemetry Application --> DB Application --> Blocks Application --> BlockStates -Application --> Metadata Application --> BeaconNode Application --> BeaconApi.Endpoint -BeaconNode -->|genesis_time,
genesis_validators_root,
fork_choice_data, time| BeaconChain -BeaconNode -->|store, head_slot, time| ForkChoice +subgraph Basic infrastructure + DB + BeaconApi.Endpoint + Telemetry + :telemetry_poller + TelemetryMetricsPrometheus +end + +subgraph Caches + Blocks + BlockStates +end + BeaconNode -->|listen_addr,
enable_discovery,
discovery_addr,
bootnodes| P2P.Libp2pPort -BeaconNode --> P2P.Peerbook -BeaconNode --> P2P.IncomingRequests -BeaconNode --> PendingBlocks BeaconNode --> SyncBlocks -BeaconNode --> Attestation -BeaconNode --> BeaconBlock -BeaconNode --> BlobSideCar -BeaconNode --> OperationsCollector BeaconNode -->|slot, head_root| ValidatorManager -BeaconNode -->|genesis_time, snapshot, votes| ExecutionChain ValidatorManager --> ValidatorN -P2P.IncomingRequests --> IncomingRequests.Handler -P2P.IncomingRequests --> IncomingRequests.Receiver - Telemetry --> :telemetry_poller Telemetry --> TelemetryMetricsPrometheus ``` @@ -48,63 +44,6 @@ Each box is a process. If it has children, it's a supervisor, with it's restart If it's a leaf in the tree, it's a GenServer, task, or other non-supervisor process. The tags in the edges/arrows are the init args passed on children init (start or restart after crash). -### High level interaction - -This is the high level interaction between the processes. - -```mermaid -graph LR - -ExecutionChain - -BlobDb -BlockDb - -subgraph "P2P" - Libp2pPort - Peerbook - IncomingRequests - Attestation - BeaconBlock - BlobSideCar - Metadata -end - -subgraph "Node" - Validator - BeaconChain - ForkChoice - PendingBlocks - OperationsCollector -end - -BeaconChain <-->|on_tick
get_fork_digest, get_| Validator -BeaconChain -->|on_tick| BeaconBlock -BeaconChain <-->|on_tick
update_fork_choice_cache| ForkChoice -BeaconBlock -->|add_block| PendingBlocks -Validator -->|get_eth1_data
to build blocks| ExecutionChain -Validator -->|publish block| Libp2pPort -Validator -->|collect, stop_collecting| Attestation -Validator -->|get slashings,
attestations,
voluntary exits|OperationsCollector -Validator -->|store_blob| BlobDb -ForkChoice -->|notify new block|Validator -ForkChoice <-->|notify new block
on_attestation|OperationsCollector -ForkChoice -->|notify new block|ExecutionChain -ForkChoice -->|store_block| BlockDb -PendingBlocks -->|on_block| ForkChoice -PendingBlocks -->|get_blob_sidecar|BlobDb -Libp2pPort <-->|gosipsub
validate_message| BlobSideCar -Libp2pPort <-->|gossipsub
validate_message
subscribe_to_topic| BeaconBlock -Libp2pPort <-->|gossipsub
validate_message
subscribe_to_topic| Attestation -Libp2pPort -->|store_blob| BlobDb -Libp2pPort -->|new_peer| Peerbook -BlobSideCar -->|store_blob| BlobDb -Attestation -->|set_attnet|Metadata -IncomingRequests -->|get seq_number|Metadata -PendingBlocks -->|penalize/get
on downloading|Peerbook -Libp2pPort -->|new_request| IncomingRequests -``` - ## P2P Events This section contains sequence diagrams representing the interaction of processes through time in response to a stimulus. The main entry point for new events is through gossip and request-response protocols, which is how nodes communicates between each other. @@ -296,10 +235,6 @@ Explained, a process that wants to request something from Libp2pPort sends a req The specific kind of command (a request) is specified, but there's nothing identifying this is a response vs any other kind of result, or the specific kind of response (e.g. a block download vs a blob download). Currently the only way this is handled differentially is because the pid is waiting for a specific kind of response and for nothing else at a time. -## Checkpoint sync - -**TO DO**: document checkpoint sync. - ## Validators Validators are separate processes. They react to: @@ -351,6 +286,85 @@ In the proposing slot: - The deposits and eth1_vote are fetched from `ExecutionChain`. - The block is signed. +## Execution Chain + +The consensus node needs to communicate with the execution client for three different reasons: + +- Fork choice updates: the execution clients needs notifications when the head is updated, and payloads need validation. For these goals, `engineAPI` is used. +- Deposit contract tracking: accounts that wish to become validators need to deposit 32ETH in the deposit contract. This happens in the execution chain, but the information needs to arrive to consensus for the validator set to be updated. +- Eth 1 votes: consensus nodes agree on a summary of the execution state and a voting mechanism is built for this. + +Let's go to this communication sections one by one. + +### Engine API: fork choice updates + +The consensus node does not live in isolation. It communicates to the execution client. We implemented, in the `ExecutionClient` module, the following primitives: + +- `notify_forkchoice_updated(fork_choice_state)`: first message sent to the execution client right after exchanging capabilities. It returns if the client is syncing or valid. +- `notify_forkchoice_updated(fork_choice_state, payload_attributes)`: sent to update the fork choice state in the execution client (finalized and head payload hash). This starts the execution payload build process. Returns a `payload_id` that will be used at block building time to get the actual execution payload. It's sent in the slot prior to proposing. It might return a null id if the execution client is still syncing. +- `get_payload(payload_id)`: returns an `{ExecutionPayload.t(), BlobsBundle.t()}` tuple that started building when `notify_forkchoice_updated` was called with payload attributes. +- `notify_new_payload(execution_payload, versioned_hashes, parent_beacon_block_root)`: when the execution client gets a new block, it needs to check if the execution payload is valid. This method is used to send that payload for verification. It may return valid, invalid, or syncing, in the case where the execution client is not yet synced. + +### Deposit contract + +Each time there's a deposit, a log is included in the execution block that can be read by the consensus layer using the `get_deposit_logs(range)` function for this. + +Each deposit has the following form: + +```elixir +%DepositData { + # BLS Credentials publicly identifying the validator. + pubkey: Types.bls_pubkey(), + # Public address where the stake rewards will be sent. + withdrawal_credentials: Types.bytes32(), + # Amount of eth deposited. + amount: Types.gwei(), + # Signature over the other fields. + signature: Types.bls_signature() +} +``` + +These deposits are aggregated into a merkle trie where each deposit is a leaf. After aggregation we can obtain: + +- A `deposit_root`: root of the merkle trie. +- A `deposit_count`: the amount of deposits that were processed in the execution layer. + +This aggregation structure is useful to send snapshots cheaply, when performing checkpoint sync. See [EIP-4881](https://eips.ethereum.org/EIPS/eip-4881). It can also be used to send cheap merkle proofs that show a deposit is part of the current deposit set. + +### Eth 1 voting + +Validators have a summarized view of the execution chain, stored in a struct called `Eth1Data`: + +```elixir +%Eth1Data{ + deposit_root: Types.root(), + deposit_count: Types.uint64(), + block_hash: Types.hash32() +} +``` + +This is the full process of how this data is included in the consensus state: + +- There's a voting period each 64 epochs. That is, 2048 slots, almost 7 hours. There's a period change when `rem(next_epoch, epochs_per_eth1_voting_period) == 0`. +- Each proposer include their view of the chain (`Eth1Data`) in the block they propose (`block.body.eth1_data`). They obtain this data from their own execution client. This is sometimes called their "eth 1 vote". +- When state transition is performed using that block, the vote is included in the `beacon_state.eth1_data_votes` array, which contains the votes for the whole voting period. If a single `eth1_data_vote` is present in more than half of the period slots, then it's now considered the current `eth1_data`, which means it's assigned asĀ `beacon_state.eth1_data` in that state transition. +- After an eth1 data vote period finishes, the `beacon_state.eth1_data_votes` are reset (the list is assigned as empty), regardless of there being a winner (new eth 1 data after the period) or not. + +Everything related to state transition is performed by beacon nodes even if they're not validators, and need no interaction with the execution chain, as they only apply blocks to states as a pure function. + +However, validators that include their view of the execution chain in a block being built, do need access to the latest blocks, as described in the [eth1 data section](https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/validator.md#eth1-data) of the validator specs. To summarize: + +- The consensus node gets blocks from the execution client and builds `Eth1Data` structs. +- A proposer at a slot `N` will need to build its eth1 vote for the voting period that started at slot `s = div(N, EPOCHS_PER_ETH1_VOTING_PERIOD)`. It will take into account the execution blocks that are `ETH1_FOLLOW_DISTANCE` behind the current voting period. +- The vote is selected using the following priority: + - An `eth1_data` that is present in the execution client and also in the beacon state `eth1_data_votes` list. If more than one match, the most frequent will be selected. That ways it tries to match a vote by a different node if present. + - If no matches are found in the beacon state votes, it will default to the latest eth1 data available in the local execution client (within the expected range). + - If nothing is found in the execution chain for the expected range, the last default is voting for the current `beacon_state.eth1_data`. + +## Checkpoint sync + +**TO DO**: document checkpoint sync. + ## Next document Let's go over [Fork Choice](fork_choice.md) to see a theoretical explanation of LMD GHOST.