@@ -4,52 +4,91 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
4
4
stores the canonical Eth1 chain for block proposing.
5
5
"""
6
6
require Logger
7
- use GenServer
8
7
9
8
alias LambdaEthereumConsensus.Execution.ExecutionClient
9
+ alias LambdaEthereumConsensus.Store.KvSchema
10
10
alias LambdaEthereumConsensus.Store.StoreDb
11
11
alias Types.Deposit
12
12
alias Types.DepositTree
13
13
alias Types.DepositTreeSnapshot
14
14
alias Types.Eth1Data
15
15
alias Types.ExecutionPayload
16
16
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 ) }
21
42
22
43
@ spec get_eth1_vote ( Types . slot ( ) ) :: { :ok , Eth1Data . t ( ) | nil } | { :error , any }
23
44
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 )
25
47
end
26
48
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
29
54
30
55
@ spec get_deposits ( Eth1Data . t ( ) , Eth1Data . t ( ) , Range . t ( ) ) ::
31
56
{ :ok , [ Deposit . t ( ) ] | nil } | { :error , any }
32
57
def get_deposits ( current_eth1_data , eth1_vote , deposit_range ) do
33
58
if Range . size ( deposit_range ) == 0 do
34
59
{ :ok , [ ] }
35
60
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 )
37
70
end
38
71
end
39
72
40
73
@ spec notify_new_block ( Types . slot ( ) , Eth1Data . t ( ) , ExecutionPayload . t ( ) ) :: :ok
41
74
def notify_new_block ( slot , eth1_data , % ExecutionPayload { } = execution_payload ) do
42
75
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 ( )
44
82
end
45
83
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
48
88
state = % {
49
89
# PERF: we could use some kind of ordered map for storing votes
50
90
eth1_data_votes: % { } ,
51
91
eth1_chain: [ ] ,
52
- genesis_time: genesis_time ,
53
92
current_eth1_data: DepositTreeSnapshot . get_eth1_data ( snapshot ) ,
54
93
deposit_tree: DepositTree . from_snapshot ( snapshot ) ,
55
94
last_period: 0
@@ -59,44 +98,14 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
59
98
60
99
StoreDb . persist_deposits_snapshot ( snapshot )
61
100
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 )
93
102
end
94
103
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
96
105
current_period = compute_period ( slot )
97
106
98
107
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 )
100
109
% { state | eth1_data_votes: % { } , eth1_chain: new_chain , last_period: current_period }
101
110
else
102
111
state
@@ -107,8 +116,8 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
107
116
% { state | eth1_chain: [ payload_info | eth1_chain ] }
108
117
end
109
118
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 )
112
121
113
122
follow_time_distance =
114
123
ChainSpec . get ( "SECONDS_PER_ETH1_BLOCK" ) * ChainSpec . get ( "ETH1_FOLLOW_DISTANCE" )
@@ -172,22 +181,23 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
172
181
end
173
182
end
174
183
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
+
176
187
defp validate_range ( _ , _ ) , do: { :error , "deposit range out of bounds" }
177
188
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 }
179
190
defp compute_eth1_vote ( % { eth1_chain: [ ] } , _ ) , do: { :ok , nil }
180
191
181
192
defp compute_eth1_vote (
182
193
% {
183
194
eth1_chain: eth1_chain ,
184
195
eth1_data_votes: seen_votes ,
185
- genesis_time: genesis_time ,
186
196
deposit_tree: deposit_tree
187
197
} ,
188
198
slot
189
199
) do
190
- period_start = voting_period_start_time ( slot , genesis_time )
200
+ period_start = voting_period_start_time ( slot )
191
201
follow_time = ChainSpec . get ( "SECONDS_PER_ETH1_BLOCK" ) * ChainSpec . get ( "ETH1_FOLLOW_DISTANCE" )
192
202
193
203
blocks_to_consider =
@@ -257,7 +267,8 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
257
267
timestamp in ( period_start - follow_time * 2 ) .. ( period_start - follow_time )
258
268
end
259
269
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! ( )
261
272
period_start_slot = slot - rem ( slot , slots_per_eth1_voting_period ( ) )
262
273
genesis_time + period_start_slot * ChainSpec . get ( "SECONDS_PER_SLOT" )
263
274
end
@@ -266,4 +277,16 @@ defmodule LambdaEthereumConsensus.Execution.ExecutionChain do
266
277
267
278
defp slots_per_eth1_voting_period ( ) ,
268
279
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
269
292
end
0 commit comments