1
1
use bitflags:: bitflags;
2
- use mina_p2p_messages:: v2:: { self , ArchiveTransitionFronntierDiff } ;
2
+ use mina_p2p_messages:: v2:: { self } ;
3
3
use node:: core:: { channels:: mpsc, thread} ;
4
4
use node:: ledger:: write:: BlockApplyResult ;
5
5
use std:: env;
6
- use std:: net:: SocketAddr ;
6
+ use std:: io:: Write ;
7
+ use std:: path:: PathBuf ;
7
8
8
9
use super :: NodeService ;
9
10
@@ -23,7 +24,7 @@ bitflags! {
23
24
24
25
impl ArchiveStorageOptions {
25
26
pub fn is_enabled ( & self ) -> bool {
26
- self . is_empty ( )
27
+ ! self . is_empty ( )
27
28
}
28
29
29
30
pub fn requires_precomputed_block ( & self ) -> bool {
@@ -60,6 +61,27 @@ impl ArchiveStorageOptions {
60
61
. to_string ( ) ,
61
62
) ;
62
63
}
64
+
65
+ if env:: var ( "AWS_DEFAULT_REGION" ) . is_err ( ) {
66
+ return Err (
67
+ "AWS_DEFAULT_REGION is required when AWS_PRECOMPUTED_STORAGE is enabled"
68
+ . to_string ( ) ,
69
+ ) ;
70
+ }
71
+
72
+ if env:: var ( "OPENMINA_AWS_BUCKET_NAME" ) . is_err ( ) {
73
+ return Err (
74
+ "OPENMINA_AWS_BUCKET_NAME is required when AWS_PRECOMPUTED_STORAGE is enabled"
75
+ . to_string ( ) ,
76
+ ) ;
77
+ }
78
+
79
+ // if env::var("OPENMINA_AWS_BUCKET_PATH").is_err() {
80
+ // return Err(
81
+ // "OPENMINA_AWS_BUCKET_PATH is required when AWS_PRECOMPUTED_STORAGE is enabled"
82
+ // .to_string(),
83
+ // );
84
+ // }
63
85
}
64
86
65
87
// TODO(adonagy): Add GCP precomputed storage validation
@@ -89,31 +111,58 @@ pub struct ArchiveService {
89
111
}
90
112
91
113
struct ArchiveServiceClients {
92
- aws_client : Option < aws_sdk_s3 :: Client > ,
114
+ aws_client : Option < ArchiveAWSClient > ,
93
115
gcp_client : Option < ( ) > ,
116
+ local_path : Option < String > ,
117
+ }
118
+
119
+ struct ArchiveAWSClient {
120
+ client : aws_sdk_s3:: Client ,
121
+ bucket_name : String ,
122
+ bucket_path : String ,
94
123
}
95
124
96
125
impl ArchiveServiceClients {
97
- async fn new ( options : & ArchiveStorageOptions ) -> Self {
126
+ async fn new ( options : & ArchiveStorageOptions , work_dir : String ) -> Self {
98
127
let aws_client = if options. uses_aws_precomputed_storage ( ) {
99
128
let config = aws_config:: load_from_env ( ) . await ;
100
- Some ( aws_sdk_s3:: Client :: new ( & config) )
129
+ let bucket_name = env:: var ( "OPENMINA_AWS_BUCKET_NAME" ) . unwrap_or_default ( ) ;
130
+ let bucket_path = env:: var ( "OPENMINA_AWS_BUCKET_PATH" ) . unwrap_or_default ( ) ;
131
+ Some ( ArchiveAWSClient {
132
+ client : aws_sdk_s3:: Client :: new ( & config) ,
133
+ bucket_name,
134
+ bucket_path,
135
+ } )
101
136
} else {
102
137
None
103
138
} ;
139
+
140
+ let local_path = if options. uses_local_precomputed_storage ( ) {
141
+ let env_path = env:: var ( "OPENMINA_LOCAL_PRECOMPUTED_STORAGE_PATH" ) ;
142
+ let default = format ! ( "{}/archive-precomputed" , work_dir) ;
143
+ Some ( env_path. unwrap_or ( default) )
144
+ } else {
145
+ None
146
+ } ;
147
+
104
148
Self {
105
149
aws_client,
106
150
gcp_client : None ,
151
+ local_path,
107
152
}
108
153
}
109
154
110
- pub fn aws_client ( & self ) -> Option < & aws_sdk_s3 :: Client > {
155
+ pub fn aws_client ( & self ) -> Option < & ArchiveAWSClient > {
111
156
self . aws_client . as_ref ( )
112
157
}
113
158
114
159
pub fn gcp_client ( & self ) -> Option < & ( ) > {
115
160
self . gcp_client . as_ref ( )
116
161
}
162
+
163
+ pub fn local_path ( & self ) -> Option < & str > {
164
+ self . local_path . as_deref ( )
165
+ }
117
166
}
118
167
119
168
impl ArchiveService {
@@ -124,23 +173,35 @@ impl ArchiveService {
124
173
#[ cfg( not( target_arch = "wasm32" ) ) ]
125
174
async fn run (
126
175
mut archive_receiver : mpsc:: UnboundedReceiver < BlockApplyResult > ,
127
- address : SocketAddr ,
128
176
options : ArchiveStorageOptions ,
177
+ work_dir : String ,
129
178
) {
179
+ use std:: { fs:: File , path:: Path } ;
180
+
130
181
use mina_p2p_messages:: v2:: PrecomputedBlock ;
182
+ use openmina_core:: NetworkConfig ;
183
+ use reqwest:: Url ;
131
184
132
- let clients = ArchiveServiceClients :: new ( & options) . await ;
185
+ let clients = ArchiveServiceClients :: new ( & options, work_dir ) . await ;
133
186
134
- while let Some ( breadcrumb) = archive_receiver. blocking_recv ( ) {
187
+ while let Some ( breadcrumb) = archive_receiver. recv ( ) . await {
135
188
if options. uses_archiver_process ( ) {
189
+ let address = std:: env:: var ( "OPENMINA_ARCHIVE_ADDRESS" )
190
+ . expect ( "OPENMINA_ARCHIVE_ADDRESS is not set" ) ;
191
+ let address = Url :: parse ( & address) . expect ( "Invalid URL" ) ;
192
+
193
+ // Convert URL to SocketAddr
194
+ let socket_addrs = address. socket_addrs ( || None ) . expect ( "Invalid URL" ) ;
195
+
196
+ let socket_addr = socket_addrs. first ( ) . expect ( "No socket address found" ) ;
136
197
let mut retries = ARCHIVE_SEND_RETRIES ;
137
198
138
199
let archive_transition_frontier_diff: v2:: ArchiveTransitionFronntierDiff =
139
200
breadcrumb. clone ( ) . try_into ( ) . unwrap ( ) ;
140
201
141
202
while retries > 0 {
142
203
match rpc:: send_diff (
143
- address ,
204
+ * socket_addr ,
144
205
v2:: ArchiveRpc :: SendDiff ( archive_transition_frontier_diff. clone ( ) ) ,
145
206
) {
146
207
Ok ( result) => {
@@ -149,9 +210,10 @@ impl ArchiveService {
149
210
summary = "Archive suddenly closed connection, retrying..."
150
211
) ;
151
212
retries -= 1 ;
152
- std :: thread :: sleep ( std:: time:: Duration :: from_millis (
213
+ tokio :: time :: sleep ( std:: time:: Duration :: from_millis (
153
214
RETRY_INTERVAL_MS ,
154
- ) ) ;
215
+ ) )
216
+ . await ;
155
217
} else {
156
218
node:: core:: warn!( summary = "Successfully sent diff to archive" ) ;
157
219
break ;
@@ -164,15 +226,25 @@ impl ArchiveService {
164
226
retries = retries
165
227
) ;
166
228
retries -= 1 ;
167
- std:: thread:: sleep ( std:: time:: Duration :: from_millis ( RETRY_INTERVAL_MS ) ) ;
229
+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( RETRY_INTERVAL_MS ) )
230
+ . await ;
168
231
}
169
232
}
170
233
}
171
234
}
172
235
173
236
if options. requires_precomputed_block ( ) {
174
- // let key =
175
- // TODO(adonagy)
237
+ let network_name = NetworkConfig :: global ( ) . name ;
238
+ let height = breadcrumb. block . height ( ) ;
239
+ let state_hash = breadcrumb. block . hash ( ) ;
240
+
241
+ let key = format ! ( "{network_name}-{height}-{state_hash}.json" ) ;
242
+
243
+ node:: core:: info!(
244
+ summary = "Uploading precomputed block to archive" ,
245
+ key = key. clone( )
246
+ ) ;
247
+
176
248
let precomputed_block: PrecomputedBlock =
177
249
if let Ok ( precomputed_block) = breadcrumb. try_into ( ) {
178
250
precomputed_block
@@ -184,16 +256,46 @@ impl ArchiveService {
184
256
} ;
185
257
186
258
if options. uses_local_precomputed_storage ( ) {
187
- // TODO(adonagy): Implement local precomputed storage
259
+ // TODO(adonagy): Cleanup the unwraps (fn that returns a Result + log the error)
260
+ if let Some ( path) = clients. local_path ( ) {
261
+ let file_path = Path :: new ( path) . join ( key. clone ( ) ) ;
262
+ let mut file = File :: create ( file_path) . unwrap ( ) ;
263
+ let json = serde_json:: to_vec ( & precomputed_block) . unwrap ( ) ;
264
+ file. write_all ( & json) . unwrap ( ) ;
265
+ } else {
266
+ node:: core:: warn!( summary = "Local precomputed storage path not set" ) ;
267
+ }
188
268
}
189
269
190
270
if options. uses_gcp_precomputed_storage ( ) {
191
271
// TODO(adonagy): Implement GCP precomputed storage
192
272
}
193
273
194
274
if options. uses_aws_precomputed_storage ( ) {
195
- let client = clients. aws_client ( ) . unwrap ( ) ;
196
- // put
275
+ if let Some ( client) = clients. aws_client ( ) {
276
+ let json = serde_json:: to_string ( & precomputed_block) . unwrap ( ) ;
277
+ let res = client
278
+ . client
279
+ . put_object ( )
280
+ . bucket ( client. bucket_name . clone ( ) )
281
+ . key ( format ! ( "{}/{}" , client. bucket_path, key) )
282
+ . body ( json. as_bytes ( ) . to_vec ( ) . into ( ) )
283
+ . send ( )
284
+ . await ;
285
+
286
+ if let Err ( e) = res {
287
+ node:: core:: warn!(
288
+ summary = "Failed to upload precomputed block to AWS" ,
289
+ error = e. to_string( )
290
+ ) ;
291
+ } else {
292
+ node:: core:: warn!(
293
+ summary = "Successfully uploaded precomputed block to AWS"
294
+ ) ;
295
+ }
296
+ } else {
297
+ node:: core:: warn!( summary = "AWS client not initialized" ) ;
298
+ }
197
299
}
198
300
}
199
301
}
@@ -209,7 +311,7 @@ impl ArchiveService {
209
311
unimplemented ! ( )
210
312
}
211
313
212
- pub fn start ( address : SocketAddr , options : ArchiveStorageOptions ) -> Self {
314
+ pub fn start ( options : ArchiveStorageOptions , work_dir : String ) -> Self {
213
315
let ( archive_sender, archive_receiver) = mpsc:: unbounded_channel :: < BlockApplyResult > ( ) ;
214
316
215
317
let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
@@ -220,7 +322,7 @@ impl ArchiveService {
220
322
thread:: Builder :: new ( )
221
323
. name ( "openmina_archive" . to_owned ( ) )
222
324
. spawn ( move || {
223
- runtime. block_on ( Self :: run ( archive_receiver, address , options ) ) ;
325
+ runtime. block_on ( Self :: run ( archive_receiver, options , work_dir ) ) ;
224
326
} )
225
327
. unwrap ( ) ;
226
328
0 commit comments