Line data Source code
1 : #include "fd_ssparse.h"
2 :
3 : #include "../../../util/log/fd_log.h"
4 : #include "../../../util/archive/fd_tar.h"
5 : #include "../../../flamenco/runtime/fd_runtime_const.h"
6 : #include "../../../flamenco/runtime/fd_system_ids.h"
7 :
8 : #include <stdlib.h>
9 : #include <errno.h>
10 :
11 0 : #define FD_SSPARSE_STATE_TAR_HEADER (0)
12 0 : #define FD_SSPARSE_STATE_SCROLL_TAR_HEADER (1)
13 0 : #define FD_SSPARSE_STATE_VERSION (2)
14 0 : #define FD_SSPARSE_STATE_MANIFEST (3)
15 0 : #define FD_SSPARSE_STATE_ACCOUNT_HEADER (4)
16 0 : #define FD_SSPARSE_STATE_ACCOUNT_DATA (5)
17 0 : #define FD_SSPARSE_STATE_ACCOUNT_PADDING (6)
18 0 : #define FD_SSPARSE_STATE_STATUS_CACHE (7)
19 0 : #define FD_SSPARSE_STATE_SCROLL_ACCOUNT_GARBAGE (8)
20 : #define FD_SSPARSE_STATE_ACCOUNT_BATCH (9)
21 :
22 : fd_ssparse_t *
23 0 : fd_ssparse_init( fd_ssparse_t * ssparse ) {
24 0 : memset( ssparse, 0, sizeof(fd_ssparse_t) );
25 0 : ssparse->state = FD_SSPARSE_STATE_TAR_HEADER;
26 0 : return ssparse;
27 0 : }
28 :
29 : static int
30 : parse_tar_header_name( char const * name,
31 : ulong * id,
32 0 : ulong * slot ) {
33 0 : char name_buf[ FD_TAR_NAME_SZ ];
34 0 : fd_memcpy( name_buf, name, FD_TAR_NAME_SZ );
35 0 : name_buf[ FD_TAR_NAME_SZ-1 ] = '\0';
36 :
37 0 : char const * ptr = name_buf;
38 :
39 0 : if( FD_UNLIKELY( strncmp( ptr, "accounts/", 9UL ) ) ) {
40 0 : *id = ULONG_MAX;
41 0 : *slot = ULONG_MAX;
42 0 : return -1;
43 0 : }
44 :
45 0 : ptr += 9UL;
46 0 : char const * next = strchr( ptr, '.' );
47 0 : if( FD_UNLIKELY( !next ) ) {
48 0 : *id = ULONG_MAX;
49 0 : *slot = ULONG_MAX;
50 0 : return -1;
51 0 : }
52 0 : errno = 0;
53 0 : char * endptr;
54 0 : *slot = strtoul( ptr, &endptr, 10 );
55 0 : if( FD_UNLIKELY( errno==ERANGE || *endptr!='.' || endptr==ptr ) ) {
56 0 : *id = ULONG_MAX;
57 0 : *slot = ULONG_MAX;
58 0 : return -1;
59 0 : }
60 :
61 0 : errno = 0;
62 0 : ptr = next + 1;
63 0 : *id = strtoul( ptr, &endptr, 10 );
64 0 : if( FD_UNLIKELY( errno==ERANGE || *endptr!='\0' || endptr==ptr ) ) {
65 0 : *id = ULONG_MAX;
66 0 : *slot = ULONG_MAX;
67 0 : return -1;
68 0 : }
69 :
70 0 : return 0;
71 0 : }
72 :
73 : static int
74 : advance_tar( fd_ssparse_t * ssparse,
75 : uchar const * data,
76 : ulong data_sz,
77 0 : fd_ssparse_advance_result_t * result ) {
78 0 : ulong consume = fd_ulong_min( data_sz, 512UL - ssparse->tar.header_bytes_consumed );
79 0 : if( FD_UNLIKELY( !consume ) ) {
80 0 : FD_LOG_WARNING(( "unexpected end of data in tar header, data_sz=%lu, header_bytes_consumed=%lu", data_sz, ssparse->tar.header_bytes_consumed ));
81 0 : return FD_SSPARSE_ADVANCE_ERROR;
82 0 : }
83 :
84 0 : fd_memcpy( ssparse->tar.header+ssparse->tar.header_bytes_consumed, data, consume );
85 0 : ssparse->bytes_consumed += consume;
86 0 : result->bytes_consumed = consume;
87 0 : ssparse->tar.header_bytes_consumed += consume;
88 :
89 0 : if( FD_UNLIKELY( ssparse->tar.header_bytes_consumed<512UL ) ) return FD_SSPARSE_ADVANCE_AGAIN;
90 :
91 0 : fd_tar_meta_t const * hdr = (fd_tar_meta_t const *)ssparse->tar.header;
92 0 : ssparse->tar.header_bytes_consumed = 0UL;
93 :
94 : /* "ustar\x00" and "ustar \x00" (overlaps with version) are both
95 : valid values for magic. These are POSIX ustar and OLDGNU versions
96 : respectively. */
97 0 : if( FD_UNLIKELY( memcmp( hdr->magic, FD_TAR_MAGIC, FD_TAR_MAGIC_SZ ) ) ) {
98 0 : int not_zero = 0;
99 0 : for( ulong i=0UL; i<512UL; i++ ) not_zero |= ssparse->tar.header[ i ];
100 0 : if( FD_UNLIKELY( not_zero ) ) {
101 0 : FD_LOG_WARNING(( "invalid tar header magic `%." FD_EXPAND_THEN_STRINGIFY(FD_TAR_MAGIC_SZ) "s`", hdr->magic ));
102 0 : return FD_SSPARSE_ADVANCE_ERROR;
103 0 : }
104 :
105 0 : if( FD_LIKELY( ssparse->flags.seen_zero_tar_frame ) ) {
106 0 : if( FD_UNLIKELY( !ssparse->flags.seen_version || !ssparse->flags.seen_manifest || !ssparse->flags.seen_status_cache ) ) {
107 0 : FD_LOG_WARNING(( "unexpected end of file before version or manifest or status cache" ));
108 0 : return FD_SSPARSE_ADVANCE_ERROR;
109 0 : }
110 :
111 0 : return FD_SSPARSE_ADVANCE_DONE;
112 0 : }
113 :
114 0 : ssparse->flags.seen_zero_tar_frame = 1;
115 0 : return FD_SSPARSE_ADVANCE_AGAIN;
116 0 : }
117 :
118 0 : if( FD_UNLIKELY( ssparse->flags.seen_zero_tar_frame ) ) {
119 0 : FD_LOG_WARNING(( "unexpected valid tar header after zero frame" ));
120 0 : return FD_SSPARSE_ADVANCE_ERROR;
121 0 : }
122 :
123 0 : ssparse->tar.file_bytes = fd_tar_meta_get_size( hdr );
124 0 : if( FD_UNLIKELY( ssparse->tar.file_bytes==ULONG_MAX ) ) {
125 0 : FD_LOG_WARNING(( "invalid tar header size %." FD_EXPAND_THEN_STRINGIFY(FD_TAR_SIZE_SZ) "s "
126 0 : "for tar header name %." FD_EXPAND_THEN_STRINGIFY(FD_TAR_NAME_SZ) "s", hdr->size, hdr->name ));
127 0 : return FD_SSPARSE_ADVANCE_ERROR;
128 0 : }
129 :
130 0 : if( FD_UNLIKELY( hdr->typeflag==FD_TAR_TYPE_DIR ) ) {
131 0 : if( FD_UNLIKELY( ssparse->tar.file_bytes ) ) {
132 0 : FD_LOG_WARNING(( "invalid tar directory entry with non-zero size %lu", ssparse->tar.file_bytes ));
133 0 : return FD_SSPARSE_ADVANCE_ERROR;
134 0 : }
135 0 : return FD_SSPARSE_ADVANCE_AGAIN;
136 0 : }
137 :
138 0 : if( FD_UNLIKELY( !fd_tar_meta_is_reg( hdr ) ) ) {
139 0 : FD_LOG_WARNING(( "invalid tar header type %d", hdr->typeflag ));
140 0 : return FD_SSPARSE_ADVANCE_ERROR;
141 0 : }
142 0 : if( FD_UNLIKELY( !ssparse->tar.file_bytes ) ) {
143 0 : FD_LOG_WARNING(( "invalid tar header size %lu", ssparse->tar.file_bytes ));
144 0 : return FD_SSPARSE_ADVANCE_ERROR;
145 0 : }
146 :
147 : /* TODO: Check every header field here for validity? */
148 :
149 0 : int desired_state;
150 0 : if( FD_LIKELY( !strncmp( hdr->name, "version", 7UL ) ) ) {
151 0 : desired_state = FD_SSPARSE_STATE_VERSION;
152 0 : if( FD_UNLIKELY( ssparse->tar.file_bytes!=5UL ) ) {
153 0 : FD_LOG_WARNING(( "invalid version file size %lu", ssparse->tar.file_bytes ));
154 0 : return FD_SSPARSE_ADVANCE_ERROR;
155 0 : }
156 0 : } else if( FD_LIKELY( !strncmp( hdr->name, "accounts/", 9UL ) ) ) {
157 0 : ssparse->account.header_bytes_consumed = 0UL;
158 0 : desired_state = FD_SSPARSE_STATE_ACCOUNT_HEADER;
159 0 : ulong id, slot;
160 0 : if( FD_UNLIKELY( -1==parse_tar_header_name( hdr->name, &id, &slot ) ) ) {
161 0 : FD_LOG_WARNING(( "invalid account append vec name %." FD_EXPAND_THEN_STRINGIFY(FD_TAR_NAME_SZ) "s", hdr->name ));
162 0 : return FD_SSPARSE_ADVANCE_ERROR;
163 0 : }
164 0 : ssparse->slot = slot;
165 0 : ssparse->acc_vec_bytes = ssparse->tar.file_bytes;
166 0 : } else if( FD_LIKELY( !strncmp( hdr->name, "snapshots/status_cache", 22UL ) ) ) desired_state = FD_SSPARSE_STATE_STATUS_CACHE;
167 0 : else if( FD_LIKELY( !strncmp( hdr->name, "snapshots/", 10UL ) ) ) {
168 0 : desired_state = FD_SSPARSE_STATE_MANIFEST;
169 0 : } else {
170 0 : FD_LOG_WARNING(( "unexpected tar header name `%." FD_EXPAND_THEN_STRINGIFY(FD_TAR_NAME_SZ) "s`", hdr->name ));
171 0 : return FD_SSPARSE_ADVANCE_ERROR;
172 0 : }
173 :
174 0 : ssparse->tar.file_bytes_consumed = 0UL;
175 :
176 0 : switch( desired_state ) {
177 0 : case FD_SSPARSE_STATE_VERSION:
178 0 : if( FD_UNLIKELY( ssparse->flags.seen_version ) ) {
179 0 : FD_LOG_WARNING(( "unexpected duplicate version file" ));
180 0 : return FD_SSPARSE_ADVANCE_ERROR;
181 0 : }
182 :
183 0 : ssparse->flags.seen_version = 1;
184 0 : ssparse->state = FD_SSPARSE_STATE_VERSION;
185 0 : break;
186 0 : case FD_SSPARSE_STATE_MANIFEST:
187 0 : if( FD_UNLIKELY( ssparse->flags.seen_manifest ) ) {
188 0 : FD_LOG_WARNING(( "unexpected duplicate manifest file" ));
189 0 : return FD_SSPARSE_ADVANCE_ERROR;
190 0 : }
191 :
192 0 : ssparse->flags.seen_manifest = 1;
193 0 : ssparse->state = FD_SSPARSE_STATE_MANIFEST;
194 0 : break;
195 0 : case FD_SSPARSE_STATE_ACCOUNT_HEADER:
196 0 : if( FD_UNLIKELY( !ssparse->flags.seen_manifest ) ) {
197 0 : FD_LOG_WARNING(( "unexpected account append vec file before manifest" ));
198 0 : return FD_SSPARSE_ADVANCE_ERROR;
199 0 : }
200 :
201 0 : ssparse->account.header_bytes_consumed = 0UL;
202 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_HEADER;
203 0 : break;
204 0 : case FD_SSPARSE_STATE_STATUS_CACHE:
205 0 : if( FD_UNLIKELY( ssparse->flags.seen_status_cache ) ) {
206 0 : FD_LOG_WARNING(( "unexpected status cache file" ));
207 0 : return FD_SSPARSE_ADVANCE_ERROR;
208 0 : }
209 :
210 0 : ssparse->flags.seen_status_cache = 1;
211 0 : ssparse->state = FD_SSPARSE_STATE_STATUS_CACHE;
212 0 : break;
213 0 : default:
214 0 : FD_LOG_ERR(( "unexpected tar header desired state %d", desired_state ));
215 0 : break;
216 0 : }
217 :
218 0 : return FD_SSPARSE_ADVANCE_AGAIN;
219 0 : }
220 :
221 : static int
222 : advance_version( fd_ssparse_t * ssparse,
223 : uchar const * data,
224 : ulong data_sz,
225 0 : fd_ssparse_advance_result_t * result ) {
226 0 : ulong consume = fd_ulong_min( data_sz, ssparse->tar.file_bytes-ssparse->tar.file_bytes_consumed );
227 0 : if( FD_UNLIKELY( !consume ) ) {
228 0 : FD_LOG_WARNING(( "unexpected end of data while parsing version file, data_sz=%lu, file_bytes_consumed=%lu, file_bytes=%lu", data_sz, ssparse->tar.file_bytes_consumed, ssparse->tar.file_bytes ));
229 0 : return FD_SSPARSE_ADVANCE_ERROR;
230 0 : }
231 :
232 0 : fd_memcpy( ssparse->version+ssparse->tar.file_bytes_consumed, data, consume );
233 :
234 0 : ssparse->tar.file_bytes_consumed += consume;
235 0 : ssparse->bytes_consumed += consume;
236 0 : result->bytes_consumed = consume;
237 :
238 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed<ssparse->tar.file_bytes ) ) return FD_SSPARSE_ADVANCE_AGAIN;
239 :
240 0 : FD_TEST( ssparse->tar.file_bytes_consumed==ssparse->tar.file_bytes );
241 0 : FD_TEST( ssparse->tar.file_bytes_consumed==5UL );
242 :
243 0 : if( FD_UNLIKELY( memcmp( ssparse->version, "1.2.0", 5UL ) ) ) {
244 0 : FD_LOG_WARNING(( "invalid version file %.*s", 5, ssparse->version ));
245 0 : return FD_SSPARSE_ADVANCE_ERROR;
246 0 : }
247 :
248 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
249 0 : return FD_SSPARSE_ADVANCE_AGAIN;
250 0 : }
251 :
252 : static int
253 : advance_status_cache( fd_ssparse_t * ssparse,
254 : uchar const * data,
255 : ulong data_sz,
256 0 : fd_ssparse_advance_result_t * result ) {
257 0 : ulong consume = fd_ulong_min( data_sz, ssparse->tar.file_bytes-ssparse->tar.file_bytes_consumed );
258 0 : if( FD_UNLIKELY( !consume ) ) {
259 0 : FD_LOG_WARNING(( "unexpected end of data while parsing status cache, data_sz=%lu, file_bytes_consumed=%lu, file_bytes=%lu", data_sz, ssparse->tar.file_bytes_consumed, ssparse->tar.file_bytes ));
260 0 : return FD_SSPARSE_ADVANCE_ERROR;
261 0 : }
262 :
263 0 : ssparse->tar.file_bytes_consumed += consume;
264 0 : ssparse->bytes_consumed += consume;
265 :
266 0 : result->bytes_consumed = consume;
267 0 : result->status_cache.data = data;
268 0 : result->status_cache.data_sz = consume;
269 0 : result->status_cache.done = ssparse->tar.file_bytes_consumed==ssparse->tar.file_bytes;
270 :
271 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed<ssparse->tar.file_bytes ) ) {
272 0 : return FD_SSPARSE_ADVANCE_STATUS_CACHE;
273 0 : }
274 0 : else { /* ssparse->tar.file_bytes_consumed==ssparse->tar.file_bytes */
275 : /* finished parsing status cache */
276 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
277 0 : return FD_SSPARSE_ADVANCE_STATUS_CACHE;
278 0 : }
279 0 : }
280 :
281 : static int
282 : advance_manifest( fd_ssparse_t * ssparse,
283 : uchar const * data,
284 : ulong data_sz,
285 0 : fd_ssparse_advance_result_t * result ) {
286 0 : ulong consume = fd_ulong_min( data_sz, ssparse->tar.file_bytes-ssparse->tar.file_bytes_consumed );
287 0 : if( FD_UNLIKELY( !consume ) ) {
288 0 : FD_LOG_WARNING(( "unexpected end of data while parsing manifest, data_sz=%lu, file_bytes_consumed=%lu, file_bytes=%lu", data_sz, ssparse->tar.file_bytes_consumed, ssparse->tar.file_bytes ));
289 0 : return FD_SSPARSE_ADVANCE_ERROR;
290 0 : }
291 :
292 0 : ssparse->tar.file_bytes_consumed += consume;
293 0 : ssparse->bytes_consumed += consume;
294 :
295 0 : result->bytes_consumed = consume;
296 0 : result->manifest.data = data;
297 0 : result->manifest.data_sz = consume;
298 :
299 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed<ssparse->tar.file_bytes ) ) return FD_SSPARSE_ADVANCE_MANIFEST;
300 :
301 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
302 0 : return FD_SSPARSE_ADVANCE_MANIFEST_DONE;
303 0 : }
304 :
305 : static int
306 : advance_next_tar( fd_ssparse_t * ssparse,
307 : uchar const * data,
308 : ulong data_sz,
309 0 : fd_ssparse_advance_result_t * result ) {
310 0 : (void)data;
311 : /* skip padding */
312 0 : ulong bytes_remaining = fd_ulong_align_up( ssparse->bytes_consumed, 512UL ) - ssparse->bytes_consumed;
313 0 : ulong pad_sz = bytes_remaining;
314 0 : pad_sz = fd_ulong_min( pad_sz, data_sz );
315 0 : if( FD_UNLIKELY( !pad_sz && bytes_remaining ) ) {
316 0 : FD_LOG_WARNING(( "unexpected end of data while parsing tar header padding, data_sz=%lu, bytes_consumed=%lu, bytes_remaining=%lu", data_sz, ssparse->bytes_consumed, bytes_remaining ));
317 0 : return FD_SSPARSE_ADVANCE_ERROR;
318 0 : }
319 :
320 0 : ssparse->bytes_consumed += pad_sz;
321 0 : result->bytes_consumed = pad_sz;
322 0 : bytes_remaining -= pad_sz;
323 :
324 0 : if( FD_LIKELY( !bytes_remaining ) ) ssparse->state = FD_SSPARSE_STATE_TAR_HEADER;
325 0 : return FD_SSPARSE_ADVANCE_AGAIN;
326 0 : }
327 :
328 : static int
329 : advance_account_batch( fd_ssparse_t * ssparse,
330 : uchar const * data,
331 : ulong data_sz,
332 0 : fd_ssparse_advance_result_t * result ) {
333 : /* Cannot create a batch unless the parser is aligned to an account. */
334 0 : if( FD_UNLIKELY( ssparse->account.header_bytes_consumed ) ) return FD_SSPARSE_ADVANCE_AGAIN;
335 :
336 : /* Each account is at least 136 bytes large. Don't attempt to create
337 : a batch unless at least 4 accounts fit. */
338 0 : ulong avail = fd_ulong_min( data_sz, ssparse->acc_vec_bytes - ssparse->tar.file_bytes_consumed );
339 0 : if( FD_UNLIKELY( avail<(4*136UL) ) ) return FD_SSPARSE_ADVANCE_AGAIN;
340 :
341 : /* Prefetch data to reduce cache misses in hot path. */
342 0 : for( ulong i=0UL; i<fd_ulong_min( avail, 4096UL ); i+=64UL ) {
343 0 : __builtin_prefetch( (uchar *)data+i, 0, 0 );
344 0 : }
345 :
346 : /* Skip over accounts until we reached EOF or batch is full */
347 0 : result->account_batch.batch_cnt = 0;
348 0 : ulong off = 0UL;
349 0 : for( ulong idx=0UL; idx<FD_SSPARSE_ACC_BATCH_MAX && off+136UL<=avail; idx++ ) {
350 0 : uchar const * acc_hdr = (uchar *)data+off;
351 :
352 : /* We want ConfigProgram accounts to go through the slow path,
353 : since they are published from there to consumers for monitoring. */
354 0 : if( FD_UNLIKELY( !memcmp( acc_hdr+64UL, fd_solana_config_program_id.key, sizeof(fd_hash_t) ) ) ) {
355 0 : if( FD_UNLIKELY( idx==0UL ) ) return FD_SSPARSE_ADVANCE_AGAIN; /* At the front of the batch, abort */
356 0 : else break; /* otherwise, break early. */
357 0 : }
358 :
359 0 : ulong acc_data_sz = fd_ulong_load_8_fast( acc_hdr+8 );
360 0 : if( FD_UNLIKELY( acc_data_sz>FD_RUNTIME_ACC_SZ_MAX ) ) {
361 0 : FD_LOG_WARNING(( "invalid account data size %lu", acc_data_sz ));
362 0 : return FD_SSPARSE_ADVANCE_ERROR;
363 0 : }
364 : /* acc_hdr[96] is the executable flag (uchar), must be 0 or 1 */
365 0 : if( FD_UNLIKELY( acc_hdr[ 96UL ]>1 ) ) {
366 0 : FD_LOG_WARNING(( "invalid account header executable %u", acc_hdr[ 96UL ] ));
367 0 : return FD_SSPARSE_ADVANCE_ERROR;
368 0 : }
369 0 : ulong next_off = off+136UL+acc_data_sz;
370 0 : ulong pad_sz = fd_ulong_align_up( ssparse->tar.file_bytes_consumed+next_off, 8UL ) -
371 0 : ( ssparse->tar.file_bytes_consumed+next_off );
372 0 : next_off += pad_sz;
373 0 : if( FD_UNLIKELY( next_off>avail ) ) break; /* account is fragmented */
374 0 : result->account_batch.batch_cnt = idx+1UL;
375 0 : result->account_batch.batch[ idx ] = acc_hdr;
376 0 : ssparse->account.header_bytes_consumed = 136UL;
377 0 : ssparse->account.data_bytes_consumed = acc_data_sz;
378 0 : ssparse->account.data_len = acc_data_sz;
379 0 : off = next_off;
380 0 : }
381 :
382 : /* Skip if no batches to process. */
383 0 : if( FD_UNLIKELY( !result->account_batch.batch_cnt ) ) {
384 0 : return FD_SSPARSE_ADVANCE_AGAIN;
385 0 : }
386 :
387 0 : ssparse->tar.file_bytes_consumed += off;
388 0 : ssparse->bytes_consumed += off;
389 0 : result->bytes_consumed = off;
390 :
391 : /* reset state */
392 :
393 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_PADDING;
394 :
395 0 : result->account_batch.slot = ssparse->slot;
396 :
397 0 : return FD_SSPARSE_ADVANCE_ACCOUNT_BATCH;
398 0 : }
399 :
400 : static int
401 : advance_account_header( fd_ssparse_t * ssparse,
402 : uchar const * data,
403 : ulong data_sz,
404 0 : fd_ssparse_advance_result_t * result ) {
405 0 : ulong consume = fd_ulong_min( 136UL-ssparse->account.header_bytes_consumed, fd_ulong_min( data_sz, ssparse->acc_vec_bytes-ssparse->tar.file_bytes_consumed ) );
406 :
407 0 : if( FD_UNLIKELY( !consume ) ) {
408 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed==ssparse->acc_vec_bytes ) ) {
409 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_ACCOUNT_GARBAGE;
410 0 : return FD_SSPARSE_ADVANCE_AGAIN;
411 0 : } else {
412 0 : FD_LOG_WARNING(( "unexpected end of data while advancing account header, data_sz=%lu, file_bytes_consumed=%lu, acc_vec_bytes=%lu", data_sz, ssparse->tar.file_bytes_consumed, ssparse->acc_vec_bytes ));
413 0 : return FD_SSPARSE_ADVANCE_ERROR;
414 0 : }
415 0 : }
416 :
417 0 : if( FD_UNLIKELY( consume<136UL ) ) {
418 0 : fd_memcpy( ssparse->account.header+ssparse->account.header_bytes_consumed, data, consume );
419 0 : } else if( ssparse->batch_enabled ) {
420 : /* fast path */
421 0 : int res = advance_account_batch( ssparse, data, data_sz, result );
422 0 : if( res==FD_SSPARSE_ADVANCE_ACCOUNT_BATCH ) return res;
423 : /* fall through and continue processing account header */
424 0 : }
425 :
426 0 : ssparse->account.header_bytes_consumed += consume;
427 0 : ssparse->tar.file_bytes_consumed += consume;
428 0 : ssparse->bytes_consumed += consume;
429 0 : result->bytes_consumed = consume;
430 :
431 0 : if( FD_UNLIKELY( ssparse->account.header_bytes_consumed<136UL ) ) return FD_SSPARSE_ADVANCE_AGAIN;
432 :
433 0 : uchar const * hdr = ssparse->account.header;
434 0 : if( FD_LIKELY( consume==136UL ) ) hdr = data;
435 :
436 0 : result->account_header.data_len = fd_ulong_load_8_fast( hdr+8UL );
437 0 : if( FD_UNLIKELY( result->account_header.data_len>FD_RUNTIME_ACC_SZ_MAX ) ) {
438 0 : FD_LOG_WARNING(( "invalid account header data length %lu", result->account_header.data_len ));
439 0 : return FD_SSPARSE_ADVANCE_ERROR;
440 0 : }
441 :
442 0 : result->account_header.pubkey = hdr+16UL;
443 0 : result->account_header.lamports = fd_ulong_load_8_fast( hdr+48UL );
444 0 : result->account_header.rent_epoch = fd_ulong_load_8_fast( hdr+56UL );
445 0 : result->account_header.owner = hdr+64UL;
446 0 : result->account_header.executable = hdr[ 96UL ];
447 0 : if( FD_UNLIKELY( result->account_header.executable>1 ) ) {
448 0 : char pubkey_str[ FD_BASE58_ENCODED_32_SZ ];
449 0 : fd_base58_encode_32( result->account_header.pubkey, NULL, pubkey_str );
450 0 : FD_LOG_WARNING(( "invalid account header executable %d for account %s", result->account_header.executable, pubkey_str ));
451 0 : return FD_SSPARSE_ADVANCE_ERROR;
452 0 : }
453 0 : result->account_header.hash = hdr+104UL;
454 0 : result->account_header.slot = ssparse->slot;
455 :
456 0 : ssparse->account.data_len = result->account_header.data_len;
457 0 : ssparse->account.data_bytes_consumed = 0UL;
458 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_DATA;
459 :
460 0 : return FD_SSPARSE_ADVANCE_ACCOUNT_HEADER;
461 0 : }
462 :
463 : static int
464 : advance_account_data( fd_ssparse_t * ssparse,
465 : uchar const * data,
466 : ulong data_sz,
467 0 : fd_ssparse_advance_result_t * result ) {
468 0 : if( FD_UNLIKELY( ssparse->account.data_bytes_consumed==ssparse->account.data_len ) ) {
469 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_PADDING;
470 0 : return FD_SSPARSE_ADVANCE_AGAIN;
471 0 : }
472 :
473 0 : ulong consume = fd_ulong_min( data_sz, ssparse->acc_vec_bytes-ssparse->tar.file_bytes_consumed );
474 0 : if( FD_UNLIKELY( !consume ) ) {
475 0 : FD_LOG_WARNING(( "account data extends beyond append vec size" ));
476 0 : return FD_SSPARSE_ADVANCE_ERROR;
477 0 : }
478 :
479 0 : consume = fd_ulong_min( consume, ssparse->account.data_len-ssparse->account.data_bytes_consumed );
480 0 : if( FD_UNLIKELY( !consume ) ) {
481 0 : FD_LOG_WARNING(( "unexpected end of data while parsing account data, data_sz=%lu, data_bytes_consumed=%lu, data_len=%lu", data_sz, ssparse->account.data_bytes_consumed, ssparse->account.data_len ));
482 0 : return FD_SSPARSE_ADVANCE_ERROR;
483 0 : }
484 :
485 0 : ssparse->tar.file_bytes_consumed += consume;
486 0 : ssparse->bytes_consumed += consume;
487 0 : ssparse->account.data_bytes_consumed += consume;
488 0 : result->bytes_consumed = consume;
489 :
490 0 : result->account_data.data_sz = consume;
491 0 : result->account_data.data = data;
492 :
493 0 : FD_TEST( ssparse->account.data_bytes_consumed<=ssparse->account.data_len );
494 0 : if( FD_LIKELY( ssparse->account.data_bytes_consumed==ssparse->account.data_len ) ) {
495 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_PADDING;
496 0 : }
497 :
498 0 : return FD_SSPARSE_ADVANCE_ACCOUNT_DATA;
499 0 : }
500 :
501 : static int
502 : advance_account_padding( fd_ssparse_t * ssparse,
503 : uchar const * data,
504 : ulong data_sz,
505 0 : fd_ssparse_advance_result_t * result ) {
506 0 : (void)data;
507 :
508 0 : ulong pad_sz = fd_ulong_align_up( ssparse->tar.file_bytes_consumed, 8UL ) - ssparse->tar.file_bytes_consumed;
509 0 : pad_sz = fd_ulong_min( pad_sz, ssparse->acc_vec_bytes - ssparse->tar.file_bytes_consumed );
510 0 : if( FD_UNLIKELY( !pad_sz ) ) {
511 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed==ssparse->acc_vec_bytes ) ) ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
512 0 : else ssparse->state = FD_SSPARSE_STATE_ACCOUNT_HEADER;
513 :
514 0 : ssparse->account.header_bytes_consumed = 0UL;
515 0 : return FD_SSPARSE_ADVANCE_AGAIN;
516 0 : }
517 :
518 0 : ulong consume = fd_ulong_min( data_sz, pad_sz );
519 0 : if( FD_UNLIKELY( !consume ) ) {
520 0 : FD_LOG_WARNING(( "unexpected end of data while parsing account padding, data_sz=%lu, file_bytes_consumed=%lu, acc_vec_bytes=%lu", data_sz, ssparse->tar.file_bytes_consumed, ssparse->acc_vec_bytes ));
521 0 : return FD_SSPARSE_ADVANCE_ERROR;
522 0 : }
523 :
524 0 : ssparse->tar.file_bytes_consumed += consume;
525 0 : ssparse->bytes_consumed += consume;
526 0 : result->bytes_consumed = consume;
527 :
528 0 : ulong remaining = fd_ulong_align_up( ssparse->tar.file_bytes_consumed, 8UL ) - ssparse->tar.file_bytes_consumed;
529 0 : if( FD_LIKELY( !remaining ) ) {
530 0 : ssparse->account.header_bytes_consumed = 0UL;
531 0 : ssparse->state = FD_SSPARSE_STATE_ACCOUNT_HEADER;
532 0 : }
533 0 : return FD_SSPARSE_ADVANCE_AGAIN;
534 0 : }
535 :
536 : static int
537 : advance_account_garbage( fd_ssparse_t * ssparse,
538 : uchar const * data,
539 : ulong data_sz,
540 0 : fd_ssparse_advance_result_t * result ) {
541 0 : (void)data;
542 0 : ulong rem = ssparse->tar.file_bytes-ssparse->tar.file_bytes_consumed;
543 0 : if( FD_UNLIKELY( !rem ) ) {
544 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
545 0 : return FD_SSPARSE_ADVANCE_AGAIN;
546 0 : }
547 :
548 0 : if( FD_UNLIKELY( !data_sz ) ) {
549 0 : FD_LOG_WARNING(( "unexpected end of data while parsing append vec garbage, data_sz=%lu, remaining_bytes=%lu", data_sz, rem ));
550 0 : return FD_SSPARSE_ADVANCE_ERROR;
551 0 : }
552 :
553 0 : ulong consume = fd_ulong_min( data_sz, rem );
554 0 : ssparse->tar.file_bytes_consumed += consume;
555 0 : ssparse->bytes_consumed += consume;
556 0 : result->bytes_consumed = consume;
557 :
558 0 : if( FD_LIKELY( ssparse->tar.file_bytes_consumed<ssparse->tar.file_bytes ) ) return FD_SSPARSE_ADVANCE_AGAIN;
559 :
560 0 : ssparse->state = FD_SSPARSE_STATE_SCROLL_TAR_HEADER;
561 0 : return FD_SSPARSE_ADVANCE_AGAIN;
562 0 : }
563 :
564 : int
565 : fd_ssparse_advance( fd_ssparse_t * ssparse,
566 : uchar const * data,
567 : ulong data_sz,
568 0 : fd_ssparse_advance_result_t * result ) {
569 0 : result->bytes_consumed = 0UL;
570 :
571 0 : switch( ssparse->state ) {
572 0 : case FD_SSPARSE_STATE_TAR_HEADER: return advance_tar( ssparse, data, data_sz, result );
573 0 : case FD_SSPARSE_STATE_SCROLL_TAR_HEADER: return advance_next_tar( ssparse, data, data_sz, result );
574 0 : case FD_SSPARSE_STATE_VERSION: return advance_version( ssparse, data, data_sz, result );
575 0 : case FD_SSPARSE_STATE_MANIFEST: return advance_manifest( ssparse, data, data_sz, result );
576 0 : case FD_SSPARSE_STATE_ACCOUNT_HEADER: return advance_account_header( ssparse, data, data_sz, result );
577 0 : case FD_SSPARSE_STATE_ACCOUNT_DATA: return advance_account_data( ssparse, data, data_sz, result );
578 0 : case FD_SSPARSE_STATE_ACCOUNT_PADDING: return advance_account_padding( ssparse, data, data_sz, result );
579 0 : case FD_SSPARSE_STATE_STATUS_CACHE: return advance_status_cache( ssparse, data, data_sz, result );
580 0 : case FD_SSPARSE_STATE_SCROLL_ACCOUNT_GARBAGE: return advance_account_garbage( ssparse, data, data_sz, result );
581 0 : default: FD_LOG_ERR(( "invalid state %d", ssparse->state ));
582 0 : }
583 0 : }
584 :
585 : void
586 : fd_ssparse_batch_enable( fd_ssparse_t * ssparse,
587 0 : int enabled ) {
588 0 : ssparse->batch_enabled = !!enabled;
589 0 : }
|