Skip to content

Commit 3efed57

Browse files
authored
feat: load validator keys from file (prototype) (#897)
1 parent 95d739b commit 3efed57

File tree

2 files changed

+71
-6
lines changed

2 files changed

+71
-6
lines changed

config/runtime.exs

+20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ switches = [
1111
testnet_dir: :string,
1212
metrics: :boolean,
1313
metrics_port: :integer,
14+
validator_file: :string,
1415
log_file: :string
1516
]
1617

@@ -34,6 +35,7 @@ jwt_path = Keyword.get(args, :execution_jwt)
3435
testnet_dir = Keyword.get(args, :testnet_dir)
3536
enable_metrics = Keyword.get(args, :metrics, false)
3637
metrics_port = Keyword.get(args, :metrics_port, if(enable_metrics, do: 9568, else: nil))
38+
validator_file = Keyword.get(args, :validator_file)
3739

3840
config :lambda_ethereum_consensus, LambdaEthereumConsensus.ForkChoice,
3941
checkpoint_sync_url: checkpoint_sync_url
@@ -108,6 +110,24 @@ config :lambda_ethereum_consensus, EngineApi,
108110
implementation: implementation,
109111
version: "2.0"
110112

113+
# Validator
114+
#
115+
# `validator_file` should be a file with two non-empty lines, the first being
116+
# the public key and the second the private key, both hex-encoded
117+
# TODO: move to ERC-2335 keystores
118+
if validator_file do
119+
[pubkey, privkey] =
120+
File.read!(validator_file)
121+
|> String.split("\n")
122+
|> Enum.reject(&(&1 == ""))
123+
|> Enum.map(&String.trim_leading(&1, "0x"))
124+
|> Enum.map(&Base.decode16!(&1, case: :mixed))
125+
126+
config :lambda_ethereum_consensus, LambdaEthereumConsensus.Validator,
127+
pubkey: pubkey,
128+
privkey: privkey
129+
end
130+
111131
# Metrics
112132

113133
# Configures metrics

lib/lambda_ethereum_consensus/validator/validator.ex

+51-6
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,69 @@ defmodule LambdaEthereumConsensus.Validator do
2323

2424
@impl true
2525
def init({slot, head_root}) do
26+
config = Application.get_env(:lambda_ethereum_consensus, __MODULE__, [])
27+
28+
validator =
29+
case {Keyword.get(config, :pubkey), Keyword.get(config, :privkey)} do
30+
{nil, nil} -> nil
31+
{pubkey, privkey} -> %{index: nil, privkey: privkey, pubkey: pubkey}
32+
end
33+
2634
state = %{
2735
slot: slot,
2836
root: head_root,
2937
duties: %{
3038
attester: {:not_computed, :not_computed}
3139
},
32-
# TODO: get validator from config
33-
validator: %{index: 55, privkey: <<652_916_760::256>>}
40+
validator: validator
3441
}
3542

3643
{:ok, state, {:continue, nil}}
3744
end
3845

3946
@impl true
47+
def handle_continue(nil, %{validator: nil} = state), do: {:noreply, state}
48+
4049
def handle_continue(nil, %{slot: slot, root: root} = state) do
50+
case try_setup_validator(state, slot, root) do
51+
nil ->
52+
Logger.error("[Validator] Public key not found in the validator set")
53+
{:noreply, state}
54+
55+
new_state ->
56+
{:noreply, new_state}
57+
end
58+
end
59+
60+
defp try_setup_validator(state, slot, root) do
4161
epoch = Misc.compute_epoch_at_slot(slot)
4262
beacon = fetch_target_state(epoch, root)
43-
duties = maybe_update_duties(state.duties, beacon, epoch, state.validator)
44-
join_subnets_for_duties(duties)
45-
log_duties(duties, state.validator.index)
46-
{:noreply, %{state | duties: duties}}
63+
64+
case fetch_validator_index(beacon, state.validator) do
65+
nil ->
66+
nil
67+
68+
validator_index ->
69+
Logger.info("[Validator] Setup for validator number #{validator_index} complete")
70+
validator = %{state.validator | index: validator_index}
71+
duties = maybe_update_duties(state.duties, beacon, epoch, validator)
72+
join_subnets_for_duties(duties)
73+
log_duties(duties, validator_index)
74+
%{state | duties: duties, validator: validator}
75+
end
4776
end
4877

4978
@impl true
79+
def handle_cast(_, %{validator: nil} = state), do: {:noreply, state}
80+
81+
# If we couldn't find the validator before, we just try again
82+
def handle_cast({:new_block, slot, head_root} = msg, %{validator: %{index: nil}} = state) do
83+
case try_setup_validator(state, slot, head_root) do
84+
nil -> {:noreply, state}
85+
new_state -> handle_cast(msg, new_state)
86+
end
87+
end
88+
5089
def handle_cast({:new_block, slot, head_root}, state) do
5190
# TODO: this doesn't take into account reorgs or empty slots
5291
new_state = update_state(state, slot, head_root)
@@ -277,6 +316,8 @@ defmodule LambdaEthereumConsensus.Validator do
277316
st
278317
end
279318

319+
defp update_with_aggregation_duty(nil, _beacon_state, _privkey), do: nil
320+
280321
defp update_with_aggregation_duty(duty, beacon_state, privkey) do
281322
proof = Utils.get_slot_signature(beacon_state, duty.slot, privkey)
282323

@@ -300,4 +341,8 @@ defmodule LambdaEthereumConsensus.Validator do
300341

301342
Map.put(duty, :subnet_id, subnet_id)
302343
end
344+
345+
defp fetch_validator_index(beacon, %{index: nil, pubkey: pk}) do
346+
beacon.validators |> Enum.find_index(&(&1.pubkey == pk))
347+
end
303348
end

0 commit comments

Comments
 (0)