@@ -3,128 +3,69 @@ defmodule LambdaEthereumConsensus.Beacon.SyncBlocks do
3
3
Performs an optimistic block sync from the finalized checkpoint to the current slot.
4
4
"""
5
5
6
- use Task
7
-
8
6
require Logger
9
7
10
8
alias LambdaEthereumConsensus.ForkChoice
11
9
alias LambdaEthereumConsensus.Libp2pPort
12
10
alias LambdaEthereumConsensus.P2P.BlockDownloader
13
- alias LambdaEthereumConsensus.P2P.Gossip
14
11
alias LambdaEthereumConsensus.StateTransition.Misc
15
- alias Types.SignedBeaconBlock
16
12
17
13
@ blocks_per_chunk 16
14
+ @ retries 50
18
15
19
- @ type chunk :: % { from: Types . slot ( ) , count: integer ( ) }
20
-
21
- def start_link ( opts ) do
22
- Task . start_link ( __MODULE__ , :run , [ opts ] )
23
- end
16
+ @ doc """
17
+ Calculates how which blocks need to be downloaded to be up to date., and launches the download
18
+ requests. Returns the amount of blocks that need to be downloaded.
24
19
25
- def run ( _opts ) do
20
+ If N blocks should be downloaded, N/16 range requests are performed. When each of those
21
+ finish, each block of those responses will be sent to libp2p port module individually using
22
+ Libp2pPort.add_block/1.
23
+ """
24
+ @ spec run ( ) :: non_neg_integer ( )
25
+ def run ( ) do
26
26
# Initial sleep for faster app start
27
- Process . sleep ( 1000 )
28
27
checkpoint = ForkChoice . get_finalized_checkpoint ( )
29
28
initial_slot = Misc . compute_start_slot_at_epoch ( checkpoint . epoch ) + 1
30
29
last_slot = ForkChoice . get_current_chain_slot ( )
31
30
32
31
# If we're around genesis, we consider ourselves synced
33
- if last_slot > 0 do
34
- perform_sync ( initial_slot , last_slot )
32
+ if last_slot <= 0 do
33
+ Logger . info ( "[Optimistic sync] At genesis. No block sync will be needed." )
34
+ 0
35
35
else
36
- start_subscriptions ( )
37
- end
38
- end
39
-
40
- @ spec perform_sync ( integer ( ) , integer ( ) ) :: :ok
41
- def perform_sync ( initial_slot , last_slot ) do
42
- Enum . chunk_every ( initial_slot .. last_slot , @ blocks_per_chunk )
43
- |> Enum . map ( fn chunk ->
44
- first_slot = List . first ( chunk )
45
- last_slot = List . last ( chunk )
46
- count = last_slot - first_slot + 1
47
- % { from: first_slot , count: count }
48
- end )
49
- |> perform_sync ( )
50
- end
51
-
52
- @ spec perform_sync ( [ chunk ( ) ] ) :: :ok
53
- def perform_sync ( chunks ) do
54
- remaining = chunks |> Stream . map ( fn % { count: c } -> c end ) |> Enum . sum ( )
55
- Logger . info ( "[Optimistic Sync] Blocks remaining: #{ remaining } " )
56
-
57
- results =
58
- chunks
59
- |> Task . async_stream (
60
- fn chunk -> fetch_blocks_by_slot ( chunk . from , chunk . count ) end ,
61
- max_concurrency: 4 ,
62
- timeout: 20_000 ,
63
- on_timeout: :kill_task
36
+ Logger . info (
37
+ "[Optimistic sync] Performing optimistic sync between slots #{ initial_slot } and #{ last_slot } , for a total of #{ last_slot - initial_slot + 1 } slots."
64
38
)
65
- |> Enum . map ( fn
66
- { :ok , result } -> result
67
- { :error , error } -> { :error , error }
68
- { :exit , :timeout } -> { :error , "timeout" }
69
- end )
70
-
71
- results
72
- |> Enum . flat_map ( fn
73
- { :ok , blocks } -> blocks
74
- _other -> [ ]
75
- end )
76
- |> tap ( fn blocks ->
77
- Logger . info ( "[Optimistic Sync] Downloaded #{ length ( blocks ) } blocks successfully." )
78
- end )
79
- |> Enum . each ( & Libp2pPort . add_block / 1 )
80
-
81
- remaining_chunks =
82
- Enum . zip ( chunks , results )
83
- |> Enum . flat_map ( fn
84
- { chunk , { :error , reason } } ->
85
- if not String . contains? ( inspect ( reason ) , "failed to dial" ) do
86
- Logger . debug (
87
- "[Optimistic Sync] Failed downloading the chunk #{ inspect ( chunk ) } . Reason: #{ inspect ( reason ) } "
88
- )
89
- end
90
39
91
- [ chunk ]
92
-
93
- _other ->
94
- [ ]
40
+ initial_slot .. last_slot
41
+ |> Enum . chunk_every ( @ blocks_per_chunk )
42
+ |> Enum . map ( fn chunk ->
43
+ first_slot = List . first ( chunk )
44
+ last_slot = List . last ( chunk )
45
+ count = last_slot - first_slot + 1
46
+
47
+ Logger . info (
48
+ "[Optimistic sync] Sending request for slots #{ first_slot } to #{ last_slot } (request size = #{ count } )."
49
+ )
50
+
51
+ BlockDownloader . request_blocks_by_range (
52
+ first_slot ,
53
+ count ,
54
+ & on_chunk_downloaded / 1 ,
55
+ @ retries
56
+ )
57
+
58
+ count
95
59
end )
96
-
97
- if Enum . empty? ( chunks ) do
98
- Logger . info ( "[Optimistic Sync] Sync completed" )
99
- start_subscriptions ( )
100
- else
101
- Process . sleep ( 1000 )
102
- perform_sync ( remaining_chunks )
60
+ |> Enum . sum ( )
103
61
end
104
62
end
105
63
106
- # TODO: handle subscription failures.
107
- defp start_subscriptions ( ) do
108
- Gossip.BeaconBlock . subscribe_to_topic ( )
109
- Gossip.BlobSideCar . subscribe_to_topics ( )
110
- Gossip.OperationsCollector . subscribe_to_topics ( )
64
+ defp on_chunk_downloaded ( { :ok , range , blocks } ) do
65
+ Libp2pPort . notify_blocks_downloaded ( range , blocks )
111
66
end
112
67
113
- @ spec fetch_blocks_by_slot ( Types . slot ( ) , non_neg_integer ( ) ) ::
114
- { :ok , [ SignedBeaconBlock . t ( ) ] } | { :error , String . t ( ) }
115
- def fetch_blocks_by_slot ( from , count ) do
116
- case BlockDownloader . request_blocks_by_range_sync ( from , count , 0 ) do
117
- { :ok , blocks } ->
118
- { :ok , blocks }
119
-
120
- { :error , error } ->
121
- if not String . contains? ( inspect ( error ) , "failed to dial" ) do
122
- Logger . debug (
123
- "Blocks download failed for slot #{ from } count #{ count } Error: #{ inspect ( error ) } "
124
- )
125
- end
126
-
127
- { :error , error }
128
- end
68
+ defp on_chunk_downloaded ( { :error , range , reason } ) do
69
+ Libp2pPort . notify_block_download_failed ( range , reason )
129
70
end
130
71
end
0 commit comments