-
Notifications
You must be signed in to change notification settings - Fork 40
feat: add support for multiple validators. #1080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8dec6f0
42af559
4305729
257be5b
2d9ee4c
0a7a84f
426c1e8
daed033
fca122a
7627b5c
2d88d6d
a3adbc5
52dee65
5896f93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -202,8 +202,16 @@ defmodule LambdaEthereumConsensus.Validator.BlockBuilder do | |
end | ||
|
||
defp get_finalized_block_hash(state) do | ||
finalized_block = Blocks.get_block!(state.finalized_checkpoint.root) | ||
finalized_hash = finalized_block.body.execution_payload.block_hash | ||
finalized_root = state.finalized_checkpoint.root | ||
|
||
finalized_hash = | ||
if finalized_root == <<0::256>> do | ||
<<0::256>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dealing with the case where there is not finalized block yet https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#forkchoicestatev1
|
||
else | ||
finalized_block = Blocks.get_block!(state.finalized_checkpoint.root) | ||
finalized_block.body.execution_payload.block_hash | ||
end | ||
|
||
{:ok, finalized_hash} | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
defmodule LambdaEthereumConsensus.Validator.Supervisor do | ||
@moduledoc false | ||
|
||
use Supervisor | ||
|
||
require Logger | ||
alias LambdaEthereumConsensus.Validator | ||
|
||
def start_link(opts) do | ||
Supervisor.start_link(__MODULE__, opts, name: __MODULE__) | ||
end | ||
|
||
@impl true | ||
def init({slot, head_root}) do | ||
config = Application.get_env(:lambda_ethereum_consensus, __MODULE__, []) | ||
|
||
keystore_dir = Keyword.get(config, :keystore_dir) | ||
keystore_pass_dir = Keyword.get(config, :keystore_pass_dir) | ||
|
||
if keystore_dir == nil or keystore_pass_dir == nil do | ||
Logger.warning( | ||
"[Validator] No keystore_dir or keystore_pass_dir provided. Validator will not start." | ||
) | ||
|
||
:ignore | ||
else | ||
validator_keys = get_validator_keys(keystore_dir, keystore_pass_dir) | ||
|
||
children = | ||
validator_keys | ||
|> Enum.map(fn {pubkey, privkey} -> | ||
Supervisor.child_spec({Validator, {slot, head_root, {pubkey, privkey}}}, | ||
id: pubkey |> Base.encode16(case: :lower) |> String.to_atom() | ||
) | ||
end) | ||
|
||
Supervisor.init(children, strategy: :one_for_one) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One validator dying should not affect others, so I applied |
||
end | ||
end | ||
|
||
@spec get_validator_keys(binary(), binary()) :: list({Bls.pubkey(), Bls.privkey()}) | ||
defp get_validator_keys(keystore_dir, keystore_pass_dir) do | ||
keystore_files = File.ls!(keystore_dir) |> Enum.sort() | ||
keystore_pass_files = File.ls!(keystore_pass_dir) |> Enum.sort() | ||
|
||
Enum.zip(keystore_files, keystore_pass_files) | ||
|> Enum.map(fn {keystore_file, keystore_pass_file} -> | ||
keystore_file = Path.join(keystore_dir, keystore_file) | ||
keystore_pass_file = Path.join(keystore_pass_dir, keystore_pass_file) | ||
|
||
# TODO: remove `try` and handle errors properly | ||
# TODO: match keystore file and pass file based on name | ||
try do | ||
Keystore.decode_from_files!(keystore_file, keystore_pass_file) | ||
rescue | ||
error -> | ||
Logger.error( | ||
"[Validator] Failed to decode keystore file: #{keystore_file}. Pass file: #{keystore_pass_file} Error: #{inspect(error)}" | ||
) | ||
|
||
nil | ||
end | ||
end) | ||
|> Enum.filter(&is_tuple/1) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,11 @@ defmodule LambdaEthereumConsensus.Validator do | |
### Public API | ||
########################## | ||
|
||
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__) | ||
def start_link({_, _, {pubkey, _}} = opts) do | ||
# TODO: if possible, use validator index instead of pubkey | ||
name = Atom.to_string(__MODULE__) <> "_" <> Base.encode16(pubkey, case: :lower) | ||
GenServer.start_link(__MODULE__, opts, name: String.to_atom(name)) | ||
end | ||
|
||
def notify_new_block(slot, head_root), | ||
do: GenServer.cast(__MODULE__, {:new_block, slot, head_root}) | ||
|
@@ -35,29 +39,39 @@ defmodule LambdaEthereumConsensus.Validator do | |
### GenServer Callbacks | ||
########################## | ||
|
||
@impl true | ||
def init({slot, head_root}) do | ||
config = Application.get_env(:lambda_ethereum_consensus, __MODULE__, []) | ||
|
||
validator = | ||
case {Keyword.get(config, :pubkey), Keyword.get(config, :privkey)} do | ||
{nil, nil} -> nil | ||
{pubkey, privkey} -> %{index: nil, privkey: privkey, pubkey: pubkey} | ||
end | ||
@type state :: %{ | ||
slot: Types.slot(), | ||
root: Types.root(), | ||
duties: %{ | ||
attester: list(:not_computed), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is |
||
proposer: :not_computed | list(Types.slot()) | ||
}, | ||
validator: any(), | ||
payload_builder: {Types.slot(), Types.root(), BlockBuilder.payload_id()} | nil | ||
} | ||
|
||
@impl true | ||
@spec init({Types.slot(), Types.root(), {Bls.pubkey(), Bls.privkey()}}) :: | ||
{:ok, state, {:continue, any}} | ||
def init({head_slot, head_root, {pubkey, privkey}}) do | ||
state = %{ | ||
slot: slot, | ||
slot: head_slot, | ||
mpaulucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
root: head_root, | ||
duties: empty_duties(), | ||
validator: validator | ||
validator: %{ | ||
pubkey: pubkey, | ||
privkey: privkey, | ||
index: nil | ||
}, | ||
payload_builder: nil | ||
} | ||
|
||
{:ok, state, {:continue, nil}} | ||
end | ||
|
||
@impl true | ||
def handle_continue(nil, %{validator: nil} = state), do: {:noreply, state} | ||
@spec handle_continue(nil, state) :: {:noreply, state} | ||
|
||
@impl true | ||
def handle_continue(nil, %{slot: slot, root: root} = state) do | ||
case try_setup_validator(state, slot, root) do | ||
nil -> | ||
|
@@ -69,6 +83,7 @@ defmodule LambdaEthereumConsensus.Validator do | |
end | ||
end | ||
|
||
@spec try_setup_validator(state, Types.slot(), Types.root()) :: state | nil | ||
defp try_setup_validator(state, slot, root) do | ||
epoch = Misc.compute_epoch_at_slot(slot) | ||
beacon = fetch_target_state(epoch, root) | ||
|
@@ -87,6 +102,8 @@ defmodule LambdaEthereumConsensus.Validator do | |
end | ||
end | ||
|
||
@spec handle_cast(any, state) :: {:noreply, state} | ||
|
||
@impl true | ||
def handle_cast(_, %{validator: nil} = state), do: {:noreply, state} | ||
|
||
|
@@ -186,6 +203,7 @@ defmodule LambdaEthereumConsensus.Validator do | |
%{state | slot: slot, root: head_root, duties: new_duties} | ||
end | ||
|
||
@spec fetch_target_state(Types.epoch(), Types.root()) :: Types.BeaconState.t() | ||
defp fetch_target_state(epoch, root) do | ||
{:ok, state} = Handlers.compute_target_checkpoint_state(epoch, root) | ||
state | ||
|
@@ -458,6 +476,8 @@ defmodule LambdaEthereumConsensus.Validator do | |
Map.put(duty, :subnet_id, subnet_id) | ||
end | ||
|
||
@spec fetch_validator_index(Types.BeaconState.t(), %{index: nil, pubkey: Bls.pubkey()}) :: | ||
non_neg_integer() | nil | ||
defp fetch_validator_index(beacon, %{index: nil, pubkey: pk}) do | ||
Enum.find_index(beacon.validators, &(&1.pubkey == pk)) | ||
end | ||
|
@@ -501,7 +521,7 @@ defmodule LambdaEthereumConsensus.Validator do | |
end | ||
|
||
defp propose(%{root: head_root, validator: validator} = state, proposed_slot) do | ||
{^proposed_slot, ^head_root, payload_id} = state.block_builder | ||
{^proposed_slot, ^head_root, payload_id} = state.payload_builder | ||
|
||
build_result = | ||
BlockBuilder.build_block( | ||
|
Uh oh!
There was an error while loading. Please reload this page.