Skip to content

Commit 8e450d0

Browse files
authored
refactor: remove execution_chain GenServer (#1185)
1 parent 6415bd1 commit 8e450d0

File tree

8 files changed

+129
-74
lines changed

8 files changed

+129
-74
lines changed

bench/deposit_tree.exs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
alias LambdaEthereumConsensus.Execution.ExecutionChain
2+
3+
# The --mode db flag is needed to run this benchmark.
4+
5+
compressed_tree = File.read!("deposit_tree_file")
6+
{:ok, encoded_tree} = :snappyer.decompress(compressed_tree)
7+
deposit_tree = :erlang.binary_to_term(encoded_tree)
8+
9+
Benchee.run(
10+
%{
11+
"ExecutionChain.put" => fn v -> ExecutionChain.put("", v) end
12+
},
13+
warmup: 2,
14+
time: 5,
15+
inputs: %{
16+
"DepositTree" => deposit_tree
17+
}
18+
)
19+
20+
Benchee.run(
21+
%{
22+
"ExecutionChain.get" => fn -> ExecutionChain.get("") end
23+
},
24+
warmup: 2,
25+
time: 5
26+
)

deposit_tree_file

1.35 KB
Binary file not shown.

lib/beacon_api/controllers/v1/beacon_controller.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ defmodule BeaconApi.V1.BeaconController do
55
alias BeaconApi.ErrorController
66
alias BeaconApi.Helpers
77
alias BeaconApi.Utils
8-
alias LambdaEthereumConsensus.ForkChoice
98
alias LambdaEthereumConsensus.Store.BlockDb
109
alias LambdaEthereumConsensus.Store.Blocks
10+
alias LambdaEthereumConsensus.Store.StoreDb
1111

1212
plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true)
1313

@@ -30,7 +30,7 @@ defmodule BeaconApi.V1.BeaconController do
3030
conn
3131
|> json(%{
3232
"data" => %{
33-
"genesis_time" => ForkChoice.get_genesis_time(),
33+
"genesis_time" => StoreDb.fetch_genesis_time!(),
3434
"genesis_validators_root" =>
3535
ChainSpec.get_genesis_validators_root() |> Utils.hex_encode(),
3636
"genesis_fork_version" => ChainSpec.get("GENESIS_FORK_VERSION") |> Utils.hex_encode()

lib/lambda_ethereum_consensus/beacon/beacon_node.ex

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconNode do
3030

3131
ForkChoice.init_store(store, time)
3232

33-
validator_children =
34-
get_validator_children(
33+
validator_manager =
34+
get_validator_manager(
3535
deposit_tree_snapshot,
3636
store.head_slot,
37-
store.head_root,
38-
store.genesis_time
37+
store.head_root
3938
)
4039

4140
children =
@@ -46,23 +45,22 @@ defmodule LambdaEthereumConsensus.Beacon.BeaconNode do
4645
{Task.Supervisor, name: PruneStatesSupervisor},
4746
{Task.Supervisor, name: PruneBlocksSupervisor},
4847
{Task.Supervisor, name: PruneBlobsSupervisor}
49-
] ++ validator_children
48+
] ++ validator_manager
5049

5150
Supervisor.init(children, strategy: :one_for_all)
5251
end
5352

54-
defp get_validator_children(nil, _, _, _) do
53+
defp get_validator_manager(nil, _, _) do
5554
Logger.warning("Deposit data not found. Validator will be disabled.")
56-
5755
[]
5856
end
5957

60-
defp get_validator_children(snapshot, slot, head_root, genesis_time) do
58+
defp get_validator_manager(snapshot, slot, head_root) do
6159
%BeaconState{eth1_data_votes: votes} = BlockStates.get_state_info!(head_root).beacon_state
60+
LambdaEthereumConsensus.Execution.ExecutionChain.init(snapshot, votes)
6261
# TODO: move checkpoint sync outside and move this to application.ex
6362
[
64-
{ValidatorManager, {slot, head_root}},
65-
{LambdaEthereumConsensus.Execution.ExecutionChain, {genesis_time, snapshot, votes}}
63+
{ValidatorManager, {slot, head_root}}
6664
]
6765
end
6866

lib/lambda_ethereum_consensus/execution/execution_chain.ex

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,91 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
44
stores the canonical Eth1 chain for block proposing.
55
"""
66
require Logger
7-
use GenServer
87

98
alias LambdaEthereumConsensus.Execution.ExecutionClient
9+
alias LambdaEthereumConsensus.Store.KvSchema
1010
alias LambdaEthereumConsensus.Store.StoreDb
1111
alias Types.Deposit
1212
alias Types.DepositTree
1313
alias Types.DepositTreeSnapshot
1414
alias Types.Eth1Data
1515
alias Types.ExecutionPayload
1616

17-
@spec start_link(Types.uint64()) :: GenServer.on_start()
18-
def start_link(opts) do
19-
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
20-
end
17+
use KvSchema, prefix: "execution_chain"
18+
19+
@type state :: %{
20+
eth1_data_votes: map(),
21+
eth1_chain: list(map()),
22+
current_eth1_data: %Types.Eth1Data{},
23+
deposit_tree: %Types.DepositTree{},
24+
last_period: integer()
25+
}
26+
27+
@impl KvSchema
28+
@spec encode_key(String.t()) :: {:ok, binary()} | {:error, binary()}
29+
def encode_key(key), do: {:ok, key}
30+
31+
@impl KvSchema
32+
@spec decode_key(binary()) :: {:ok, String.t()} | {:error, binary()}
33+
def decode_key(key), do: {:ok, key}
34+
35+
@impl KvSchema
36+
@spec encode_value(map()) :: {:ok, binary()} | {:error, binary()}
37+
def encode_value(state), do: {:ok, :erlang.term_to_binary(state)}
38+
39+
@impl KvSchema
40+
@spec decode_value(binary()) :: {:ok, map()} | {:error, binary()}
41+
def decode_value(bin), do: {:ok, :erlang.binary_to_term(bin)}
2142

2243
@spec get_eth1_vote(Types.slot()) :: {:ok, Eth1Data.t() | nil} | {:error, any}
2344
def get_eth1_vote(slot) do
24-
GenServer.call(__MODULE__, {:get_eth1_vote, slot})
45+
state = fetch_execution_state!()
46+
compute_eth1_vote(state, slot)
2547
end
2648

27-
@spec get_eth1_vote(Types.slot()) :: DepositTreeSnapshot.t()
28-
def get_deposit_snapshot(), do: GenServer.call(__MODULE__, :get_deposit_snapshot)
49+
@spec get_deposit_snapshot() :: DepositTreeSnapshot.t()
50+
def get_deposit_snapshot() do
51+
state = fetch_execution_state!()
52+
DepositTree.get_snapshot(state.deposit_tree)
53+
end
2954

3055
@spec get_deposits(Eth1Data.t(), Eth1Data.t(), Range.t()) ::
3156
{:ok, [Deposit.t()] | nil} | {:error, any}
3257
def get_deposits(current_eth1_data, eth1_vote, deposit_range) do
3358
if Range.size(deposit_range) == 0 do
3459
{:ok, []}
3560
else
36-
GenServer.call(__MODULE__, {:get_deposits, current_eth1_data, eth1_vote, deposit_range})
61+
state = fetch_execution_state!()
62+
votes = state.eth1_data_votes
63+
64+
eth1_data =
65+
if Map.has_key?(votes, eth1_vote) and has_majority?(votes, eth1_vote),
66+
do: eth1_vote,
67+
else: current_eth1_data
68+
69+
compute_deposits(state, eth1_data, deposit_range)
3770
end
3871
end
3972

4073
@spec notify_new_block(Types.slot(), Eth1Data.t(), ExecutionPayload.t()) :: :ok
4174
def notify_new_block(slot, eth1_data, %ExecutionPayload{} = execution_payload) do
4275
payload_info = Map.take(execution_payload, [:block_hash, :block_number, :timestamp])
43-
GenServer.cast(__MODULE__, {:new_block, slot, eth1_data, payload_info})
76+
77+
fetch_execution_state!()
78+
|> prune_state(slot)
79+
|> update_state_with_payload(payload_info)
80+
|> update_state_with_vote(eth1_data)
81+
|> persist_execution_state()
4482
end
4583

46-
@impl true
47-
def init({genesis_time, %DepositTreeSnapshot{} = snapshot, eth1_votes}) do
84+
@doc """
85+
Initializes the table in the db by storing the initial state of the execution chain.
86+
"""
87+
def init(%DepositTreeSnapshot{} = snapshot, eth1_votes) do
4888
state = %{
4989
# PERF: we could use some kind of ordered map for storing votes
5090
eth1_data_votes: %{},
5191
eth1_chain: [],
52-
genesis_time: genesis_time,
5392
current_eth1_data: DepositTreeSnapshot.get_eth1_data(snapshot),
5493
deposit_tree: DepositTree.from_snapshot(snapshot),
5594
last_period: 0
@@ -59,44 +98,14 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
5998

6099
StoreDb.persist_deposits_snapshot(snapshot)
61100

62-
{:ok, updated_state}
63-
end
64-
65-
@impl true
66-
def handle_call({:get_eth1_vote, slot}, _from, state) do
67-
{:reply, compute_eth1_vote(state, slot), state}
68-
end
69-
70-
@impl true
71-
def handle_call(:get_deposit_snapshot, _from, state) do
72-
{:reply, DepositTree.get_snapshot(state.deposit_tree), state}
73-
end
74-
75-
def handle_call({:get_deposits, current_eth1_data, eth1_vote, deposit_range}, _from, state) do
76-
votes = state.eth1_data_votes
77-
78-
eth1_data =
79-
if Map.has_key?(votes, eth1_vote) and has_majority?(votes, eth1_vote),
80-
do: eth1_vote,
81-
else: current_eth1_data
82-
83-
{:reply, compute_deposits(state, eth1_data, deposit_range), state}
84-
end
85-
86-
@impl true
87-
def handle_cast({:new_block, slot, eth1_data, payload_info}, state) do
88-
state
89-
|> prune_state(slot)
90-
|> update_state_with_payload(payload_info)
91-
|> update_state_with_vote(eth1_data)
92-
|> then(&{:noreply, &1})
101+
persist_execution_state(updated_state)
93102
end
94103

95-
defp prune_state(%{genesis_time: genesis_time, last_period: last_period} = state, slot) do
104+
defp prune_state(%{last_period: last_period} = state, slot) do
96105
current_period = compute_period(slot)
97106

98107
if current_period > last_period do
99-
new_chain = drop_old_payloads(state.eth1_chain, genesis_time, slot)
108+
new_chain = drop_old_payloads(state.eth1_chain, slot)
100109
%{state | eth1_data_votes: %{}, eth1_chain: new_chain, last_period: current_period}
101110
else
102111
state
@@ -107,8 +116,8 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
107116
%{state | eth1_chain: [payload_info | eth1_chain]}
108117
end
109118

110-
defp drop_old_payloads(eth1_chain, genesis_time, slot) do
111-
period_start = voting_period_start_time(slot, genesis_time)
119+
defp drop_old_payloads(eth1_chain, slot) do
120+
period_start = voting_period_start_time(slot)
112121

113122
follow_time_distance =
114123
ChainSpec.get("SECONDS_PER_ETH1_BLOCK") * ChainSpec.get("ETH1_FOLLOW_DISTANCE")
@@ -172,22 +181,23 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
172181
end
173182
end
174183

175-
defp validate_range(%{deposit_count: count}, _..deposit_end) when deposit_end >= count, do: :ok
184+
defp validate_range(%{deposit_count: count}, _..deposit_end//_) when deposit_end >= count,
185+
do: :ok
186+
176187
defp validate_range(_, _), do: {:error, "deposit range out of bounds"}
177188

178-
defp compute_eth1_vote(%{eth1_data_votes: []}, _), do: {:ok, nil}
189+
defp compute_eth1_vote(%{eth1_data_votes: map}, _) when map == %{}, do: {:ok, nil}
179190
defp compute_eth1_vote(%{eth1_chain: []}, _), do: {:ok, nil}
180191

181192
defp compute_eth1_vote(
182193
%{
183194
eth1_chain: eth1_chain,
184195
eth1_data_votes: seen_votes,
185-
genesis_time: genesis_time,
186196
deposit_tree: deposit_tree
187197
},
188198
slot
189199
) do
190-
period_start = voting_period_start_time(slot, genesis_time)
200+
period_start = voting_period_start_time(slot)
191201
follow_time = ChainSpec.get("SECONDS_PER_ETH1_BLOCK") * ChainSpec.get("ETH1_FOLLOW_DISTANCE")
192202

193203
blocks_to_consider =
@@ -257,7 +267,8 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
257267
timestamp in (period_start - follow_time * 2)..(period_start - follow_time)
258268
end
259269

260-
defp voting_period_start_time(slot, genesis_time) do
270+
defp voting_period_start_time(slot) do
271+
genesis_time = StoreDb.fetch_genesis_time!()
261272
period_start_slot = slot - rem(slot, slots_per_eth1_voting_period())
262273
genesis_time + period_start_slot * ChainSpec.get("SECONDS_PER_SLOT")
263274
end
@@ -266,4 +277,16 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
266277

267278
defp slots_per_eth1_voting_period(),
268279
do: ChainSpec.get("EPOCHS_PER_ETH1_VOTING_PERIOD") * ChainSpec.get("SLOTS_PER_EPOCH")
280+
281+
@spec persist_execution_state(state()) :: :ok | {:error, binary()}
282+
defp persist_execution_state(state), do: put("", state)
283+
284+
@spec fetch_execution_state() :: {:ok, state()} | {:error, binary()} | :not_found
285+
defp fetch_execution_state(), do: get("")
286+
287+
@spec fetch_execution_state!() :: state()
288+
defp fetch_execution_state!() do
289+
{:ok, state} = fetch_execution_state()
290+
state
291+
end
269292
end

lib/lambda_ethereum_consensus/fork_choice/fork_choice.ex

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,10 @@ defmodule LambdaEthereumConsensus.ForkChoice do
114114
persist_store(new_store)
115115
end
116116

117-
@spec get_genesis_time() :: Types.uint64()
118-
def get_genesis_time() do
119-
%{genesis_time: genesis_time} = fetch_store!()
120-
genesis_time
121-
end
122-
123117
@spec get_current_chain_slot() :: Types.slot()
124118
def get_current_chain_slot() do
125119
time = Clock.get_current_time()
126-
genesis_time = get_genesis_time()
120+
genesis_time = StoreDb.fetch_genesis_time!()
127121
compute_current_slot(time, genesis_time)
128122
end
129123

lib/lambda_ethereum_consensus/store/store_db.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ defmodule LambdaEthereumConsensus.Store.StoreDb do
2121
end)
2222
end
2323

24+
@spec fetch_genesis_time() :: {:ok, Types.uint64()} | :not_found
25+
def fetch_genesis_time() do
26+
with {:ok, store} <- fetch_store() do
27+
store.genesis_time
28+
end
29+
end
30+
31+
@spec fetch_genesis_time!() :: Types.uint64()
32+
def fetch_genesis_time!() do
33+
{:ok, %{genesis_time: genesis_time}} = fetch_store()
34+
genesis_time
35+
end
36+
2437
@spec fetch_deposits_snapshot() :: {:ok, Types.DepositTreeSnapshot.t()} | :not_found
2538
def fetch_deposits_snapshot(), do: get(@snapshot_prefix)
2639

test/unit/beacon_api/beacon_api_v1_test.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule Unit.BeaconApiTest.V1 do
88
alias LambdaEthereumConsensus.ForkChoice
99
alias LambdaEthereumConsensus.Store.BlockDb
1010
alias LambdaEthereumConsensus.Store.Db
11+
alias LambdaEthereumConsensus.Store.StoreDb
1112
alias Types.BlockInfo
1213

1314
@moduletag :beacon_api_case
@@ -35,7 +36,7 @@ defmodule Unit.BeaconApiTest.V1 do
3536
start_link_supervised!({Db, dir: tmp_dir})
3637

3738
patch(ForkChoice, :get_current_status_message, status_message)
38-
patch(ForkChoice, :get_genesis_time, 42)
39+
patch(StoreDb, :fetch_genesis_time!, 42)
3940

4041
:ok
4142
end
@@ -132,7 +133,7 @@ defmodule Unit.BeaconApiTest.V1 do
132133
test "get genesis data" do
133134
expected_response = %{
134135
"data" => %{
135-
"genesis_time" => ForkChoice.get_genesis_time(),
136+
"genesis_time" => StoreDb.fetch_genesis_time!(),
136137
"genesis_validators_root" =>
137138
ChainSpec.get_genesis_validators_root() |> Utils.hex_encode(),
138139
"genesis_fork_version" => ChainSpec.get("GENESIS_FORK_VERSION") |> Utils.hex_encode()

0 commit comments

Comments
 (0)