Line data Source code
1 : #include "fd_solcap_writer.h"
2 : #include "fd_solcap.pb.h"
3 : #include "fd_solcap_proto.h"
4 : #include "../nanopb/pb_encode.h"
5 :
6 : #if !FD_HAS_HOSTED
7 : #error "fd_solcap_writer requires FD_HAS_HOSTED"
8 : #endif
9 :
10 : #include <errno.h>
11 : #include <stdio.h>
12 :
13 : /* Note on suffixes:
14 :
15 : goff: file offset (as returned by fseek)
16 : foff: file offset from beginning of solcap stream
17 : coff: file offset from beginning of current chunk */
18 :
19 : /* fd_solcap_writer is the state of a capture writer. Currently, it
20 : is only able to capture the bank hash pre-image and chagned accounts.
21 :
22 : The writer progresses with each API call to the writer functions.
23 :
24 : Typically, the order is the following:
25 :
26 : - fd_solcap_writer_set_slot advances to the next slot. If there was
27 : a previous slot in progress but not finished, discards buffers.
28 : - fd_solcap_write_account writes an account chunk and buffers an
29 : entry for the accounts table.
30 : - fd_solcap_write_bank_preimage flushes the buffered accounts table
31 : and writes the preimage chunk. Slot is finished and ready for
32 : next iteration. */
33 :
34 : struct fd_solcap_writer {
35 : FILE * file;
36 :
37 : /* Number of bytes between start of file and start of stream.
38 : Usually 0. Non-zero if the bank capture is contained in some
39 : other file format. */
40 : ulong stream_goff;
41 :
42 : /* In-flight write of accounts table.
43 : account_idx==0UL implies no chunk header has been written yet.
44 : account_idx>=0UL implies AccountTable chunk write is pending.
45 : account_idx>=FD_SOLCAP_ACC_TBL_CNT implies that AccountTable is
46 : unable to fit records. Table record will be skipped. */
47 :
48 : ulong slot;
49 : fd_solcap_account_tbl_t accounts[ FD_SOLCAP_ACC_TBL_CNT ];
50 : uint account_idx;
51 : ulong account_table_goff;
52 :
53 : ulong first_slot;
54 : };
55 :
56 : /* FTELL_BAIL calls ftell on the given file, and bails the current
57 : function with return code EIO if it fails. */
58 :
59 : #define FTELL_BAIL( file ) \
60 0 : (__extension__({ \
61 0 : long n = ftell( (file) ); \
62 0 : if( FD_UNLIKELY( n<0L ) ) { \
63 0 : FD_LOG_WARNING(( "ftell failed (%d-%s)", \
64 0 : errno, strerror( errno ) )); \
65 0 : return EIO; \
66 0 : } \
67 0 : (ulong)n; \
68 0 : }))
69 :
70 : /* FSEEK_BAIL calls fseek on the given file, and bails the current
71 : function with return code EIO if it fails. */
72 :
73 : #define FSEEK_BAIL( file, off, whence ) \
74 0 : (__extension__({ \
75 0 : int err = fseek( (file), (off), (whence) ); \
76 0 : if( FD_UNLIKELY( err<0L ) ) { \
77 0 : FD_LOG_WARNING(( "fseek failed (%d-%s)", \
78 0 : errno, strerror( errno ) )); \
79 0 : return EIO; \
80 0 : } \
81 0 : 0; \
82 0 : }))
83 :
84 : /* FWRITE_BAIL calls fwrite on the given file, and bails the current
85 : function with return code EIO if it fails. */
86 :
87 : #define FWRITE_BAIL( ptr, sz, cnt, file ) \
88 0 : (__extension__({ \
89 0 : ulong _cnt = (cnt); \
90 0 : ulong n = fwrite( (ptr), (sz), _cnt, (file) ); \
91 0 : if( FD_UNLIKELY( n!=_cnt ) ) { \
92 0 : FD_LOG_WARNING(( "fwrite failed (%d-%s)", \
93 0 : errno, strerror( errno ) )); \
94 0 : return EIO; \
95 0 : } \
96 0 : 0; \
97 0 : }))
98 :
99 : /* _skip_file writes zeros to the file */
100 :
101 : static int
102 : _skip_file( FILE * file,
103 0 : ulong skip ) {
104 0 : if (skip == 0) return 0;
105 :
106 0 : uchar zero[ skip ];
107 0 : fd_memset( zero, 0, skip );
108 :
109 0 : FWRITE_BAIL( zero, 1UL, skip, file );
110 0 : return 0;
111 0 : }
112 :
113 : #define FSKIP_BAIL( file, skip ) \
114 0 : do { \
115 0 : int err = _skip_file( (file), (skip) ); \
116 0 : if( FD_UNLIKELY( err!=0 ) ) return err; \
117 0 : } while(0)
118 :
119 : /* _align_file pads file with zero up to meet given align requirement.
120 : align is a positive power of two. */
121 :
122 : static int
123 : _align_file( FILE * file,
124 0 : ulong align ) {
125 0 : ulong pos = FTELL_BAIL( file );
126 0 : ulong skip = fd_ulong_align_up( pos, align ) - pos;
127 0 : return _skip_file( file, skip );
128 0 : }
129 :
130 : #define FALIGN_BAIL( file, align ) \
131 0 : do { \
132 0 : int err = _align_file( (file), (align) ); \
133 0 : if( FD_UNLIKELY( err!=0 ) ) return err; \
134 0 : } while(0)
135 :
136 :
137 : ulong
138 0 : fd_solcap_writer_align( void ) {
139 0 : return alignof(fd_solcap_writer_t);
140 0 : }
141 :
142 : ulong
143 0 : fd_solcap_writer_footprint( void ) {
144 0 : return sizeof(fd_solcap_writer_t);
145 0 : }
146 :
147 : fd_solcap_writer_t *
148 0 : fd_solcap_writer_new( void * mem ) {
149 :
150 0 : if( FD_UNLIKELY( !mem ) ) {
151 0 : FD_LOG_WARNING(( "NULL mem" ));
152 0 : return NULL;
153 0 : }
154 :
155 0 : memset( mem, 0, sizeof(fd_solcap_writer_t) );
156 0 : return (fd_solcap_writer_t *)mem;
157 0 : }
158 :
159 : void *
160 0 : fd_solcap_writer_delete( fd_solcap_writer_t * writer ) {
161 :
162 0 : if( FD_UNLIKELY( !writer ) ) return NULL;
163 :
164 0 : writer->file = NULL;
165 0 : return writer;
166 0 : }
167 :
168 :
169 : fd_solcap_writer_t *
170 : fd_solcap_writer_init( fd_solcap_writer_t * writer,
171 0 : void * file ) {
172 :
173 0 : if( FD_UNLIKELY( !writer ) ) {
174 0 : FD_LOG_WARNING(( "NULL writer" ));
175 0 : return NULL;
176 0 : }
177 0 : if( FD_UNLIKELY( !file ) ) {
178 0 : FD_LOG_WARNING(( "NULL file" ));
179 0 : return NULL;
180 0 : }
181 :
182 : /* Leave space for file headers */
183 :
184 0 : long pos = ftell( file );
185 0 : if( FD_UNLIKELY( pos<0L ) ) {
186 0 : FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) ));
187 0 : return NULL;
188 0 : }
189 0 : ulong stream_goff = (ulong)pos;
190 :
191 0 : uchar zero[ FD_SOLCAP_FHDR_SZ ] = {0};
192 0 : ulong n = fwrite( zero, FD_SOLCAP_FHDR_SZ, 1UL, file );
193 0 : if( FD_UNLIKELY( n!=1UL ) ) {
194 0 : FD_LOG_WARNING(( "fwrite failed (%d-%s)", errno, strerror( errno ) ));
195 0 : return NULL;
196 0 : }
197 :
198 : /* Init writer */
199 0 : writer->file = file;
200 0 : writer->stream_goff = stream_goff;
201 :
202 0 : return writer;
203 0 : }
204 :
205 : /* fd_solcap_writer_flush writes the file header. */
206 :
207 : fd_solcap_writer_t *
208 0 : fd_solcap_writer_flush( fd_solcap_writer_t * writer ) {
209 :
210 0 : if( FD_LIKELY( !writer ) ) return NULL;
211 :
212 : /* Flush stream */
213 0 : fflush( writer->file );
214 :
215 : /* Remember stream cursor */
216 :
217 0 : long cursor = ftell( writer->file );
218 0 : if( FD_UNLIKELY( cursor<0L ) ) {
219 0 : FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) ));
220 0 : return NULL;
221 0 : }
222 :
223 : /* Construct file header */
224 :
225 0 : fd_solcap_FileMeta fmeta = {
226 0 : .first_slot = writer->first_slot,
227 0 : .slot_cnt = (ulong)fd_long_max( 0L, (long)writer->slot - (long)writer->first_slot ),
228 0 : .main_block_magic = FD_SOLCAP_V1_BANK_MAGIC,
229 0 : };
230 :
231 0 : uchar meta[ 128UL ];
232 0 : pb_ostream_t stream = pb_ostream_from_buffer( meta, sizeof(meta) );
233 0 : if( FD_UNLIKELY( !pb_encode( &stream, fd_solcap_FileMeta_fields, &fmeta ) ) ) {
234 0 : FD_LOG_WARNING(( "pb_encode failed (%s)", PB_GET_ERROR(&stream) ));
235 0 : return NULL;
236 0 : }
237 :
238 0 : fd_solcap_fhdr_t fhdr = {
239 0 : .magic = FD_SOLCAP_V1_FILE_MAGIC,
240 0 : .chunk0_foff = FD_SOLCAP_FHDR_SZ,
241 0 : .meta_sz = (uint)stream.bytes_written,
242 0 : };
243 :
244 : /* Write out file headers */
245 :
246 0 : if( FD_UNLIKELY( 0!=fseek( writer->file, (long)writer->stream_goff, SEEK_SET ) ) ) {
247 0 : FD_LOG_WARNING(( "fseek failed (%d-%s)", errno, strerror( errno ) ));
248 0 : return NULL;
249 0 : }
250 :
251 0 : if( FD_UNLIKELY( 1UL!=fwrite( &fhdr, sizeof(fd_solcap_fhdr_t), 1UL, writer->file ) ) ) {
252 0 : FD_LOG_WARNING(( "fwrite file header failed (%d-%s)", errno, strerror( errno ) ));
253 0 : return NULL;
254 0 : }
255 :
256 0 : if( FD_UNLIKELY( stream.bytes_written != fwrite( meta, 1UL, stream.bytes_written, writer->file ) ) ) {
257 0 : FD_LOG_WARNING(( "fwrite file meta failed (%d-%s)", ferror( writer->file ), strerror( ferror( writer->file ) ) ));
258 0 : return NULL;
259 0 : }
260 :
261 : /* Restore stream cursor */
262 :
263 0 : if( FD_UNLIKELY( 0!=fseek( writer->file, cursor, SEEK_SET ) ) ) {
264 0 : FD_LOG_WARNING(( "fseek failed (%d-%s)", errno, strerror( errno ) ));
265 0 : return NULL;
266 0 : }
267 :
268 0 : return writer;
269 0 : }
270 :
271 : /* fd_solcap_flush_account_table writes the buffered account table out
272 : to the stream. */
273 :
274 : static int
275 0 : fd_solcap_flush_account_table( fd_solcap_writer_t * writer ) {
276 :
277 : /* Only flush if at least one account present. */
278 :
279 0 : if( writer->account_idx == 0UL ) return 0;
280 :
281 : /* Skip if table was overflowed. */
282 :
283 : /* FIXME: This breaks account recording for epoch boundaries and needs to be fixed */
284 0 : if( writer->account_idx >= FD_SOLCAP_ACC_TBL_CNT ) {
285 0 : FD_LOG_WARNING(( "too many records in solcap accounts table - try increasing FD_SOLCAP_ACC_TBL_CNT" ));
286 0 : writer->account_idx = 0UL;
287 0 : return 0;
288 0 : }
289 :
290 : /* Leave space for header */
291 :
292 0 : ulong chunk_goff = FTELL_BAIL( writer->file );
293 0 : FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) );
294 :
295 : /* Translate account table to chunk-relative addressing */
296 :
297 0 : for( uint i=0U; i<writer->account_idx; i++ )
298 0 : writer->accounts[i].acc_coff -= (long)chunk_goff;
299 :
300 : /* Write account table (at beginning of chunk) */
301 :
302 0 : ulong account_table_coff = sizeof(fd_solcap_chunk_t);
303 0 : ulong account_table_cnt = writer->account_idx;
304 :
305 0 : FWRITE_BAIL( writer->accounts,
306 0 : sizeof(fd_solcap_account_tbl_t),
307 0 : account_table_cnt,
308 0 : writer->file );
309 :
310 : /* Serialize account chunk metadata */
311 :
312 0 : ulong meta_goff = FTELL_BAIL( writer->file );
313 0 : fd_solcap_AccountTableMeta meta = {
314 0 : .slot = writer->slot,
315 0 : .account_table_coff = account_table_coff,
316 0 : .account_table_cnt = account_table_cnt
317 0 : };
318 :
319 0 : uchar encoded[ FD_SOLCAP_ACTB_META_FOOTPRINT ];
320 0 : pb_ostream_t stream = pb_ostream_from_buffer( encoded, sizeof(encoded) );
321 0 : if( FD_UNLIKELY( !pb_encode( &stream, fd_solcap_AccountTableMeta_fields, &meta ) ) ) {
322 0 : FD_LOG_WARNING(( "pb_encode failed (%s)", PB_GET_ERROR(&stream) ));
323 0 : return EPROTO;
324 0 : }
325 :
326 0 : FWRITE_BAIL( encoded, 1UL, stream.bytes_written, writer->file );
327 0 : FALIGN_BAIL( writer->file, 8UL );
328 :
329 : /* Serialize chunk header */
330 :
331 0 : ulong chunk_end_goff = FTELL_BAIL( writer->file );
332 :
333 0 : fd_solcap_chunk_t chunk = {
334 0 : .magic = FD_SOLCAP_V1_ACTB_MAGIC,
335 0 : .meta_coff = (uint)( meta_goff - chunk_goff ),
336 0 : .meta_sz = (uint)stream.bytes_written,
337 0 : .total_sz = chunk_end_goff - chunk_goff
338 0 : };
339 :
340 : /* Write out chunk */
341 :
342 0 : FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET );
343 0 : FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file );
344 :
345 : /* Restore stream cursor */
346 :
347 0 : FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET );
348 :
349 : /* Wind up for next iteration */
350 :
351 0 : writer->account_table_goff = chunk_goff;
352 0 : writer->account_idx = 0U;
353 :
354 0 : return 0;
355 0 : }
356 :
357 : int
358 : fd_solcap_write_account( fd_solcap_writer_t * writer,
359 : void const * key,
360 : fd_solana_account_meta_t const * meta,
361 : void const * data,
362 : ulong data_sz,
363 0 : void const * hash ) {
364 :
365 0 : if( FD_LIKELY( !writer ) ) return 0;
366 :
367 0 : fd_solcap_account_tbl_t rec[1];
368 0 : memset( rec, 0, sizeof(fd_solcap_account_tbl_t) );
369 0 : memcpy( rec->key, key, 32UL );
370 0 : memcpy( rec->hash, hash, 32UL );
371 :
372 0 : fd_solcap_AccountMeta meta_pb[1] = {{
373 0 : .lamports = meta->lamports,
374 0 : .rent_epoch = meta->rent_epoch,
375 0 : .executable = meta->executable,
376 0 : .data_sz = data_sz,
377 0 : }};
378 0 : memcpy( meta_pb->owner, meta->owner, 32UL );
379 :
380 0 : return fd_solcap_write_account2( writer, rec, meta_pb, data, data_sz );
381 0 : }
382 :
383 : int
384 : fd_solcap_write_account2( fd_solcap_writer_t * writer,
385 : fd_solcap_account_tbl_t const * tbl,
386 : fd_solcap_AccountMeta * meta_pb,
387 : void const * data,
388 0 : ulong data_sz ) {
389 :
390 0 : if( FD_LIKELY( !writer ) ) return 0;
391 :
392 : /* Locate chunk */
393 :
394 0 : ulong chunk_goff = FTELL_BAIL( writer->file );
395 :
396 : /* Write data */
397 :
398 0 : ulong data_coff = sizeof(fd_solcap_chunk_t);
399 0 : FSKIP_BAIL ( writer->file, data_coff );
400 0 : FWRITE_BAIL( data, 1UL, data_sz, writer->file );
401 0 : FALIGN_BAIL( writer->file, 8UL );
402 :
403 : /* Serialize account meta */
404 :
405 0 : ulong meta_goff = FTELL_BAIL( writer->file );
406 :
407 0 : meta_pb->slot = writer->slot;
408 0 : meta_pb->data_coff = (long)data_coff;
409 0 : meta_pb->data_sz = data_sz;
410 :
411 0 : uchar meta_pb_enc[ FD_SOLCAP_ACCOUNT_META_FOOTPRINT ];
412 0 : pb_ostream_t stream = pb_ostream_from_buffer( meta_pb_enc, sizeof(meta_pb_enc) );
413 0 : FD_TEST( pb_encode( &stream, fd_solcap_AccountMeta_fields, meta_pb ) );
414 :
415 : /* Write account meta */
416 :
417 0 : ulong meta_coff = meta_goff - chunk_goff;
418 0 : FWRITE_BAIL( meta_pb_enc, 1UL, stream.bytes_written, writer->file );
419 0 : FALIGN_BAIL( writer->file, 8UL );
420 :
421 : /* Remember account table entry */
422 :
423 0 : if( writer->account_idx < FD_SOLCAP_ACC_TBL_CNT ) {
424 0 : fd_solcap_account_tbl_t * account = &writer->accounts[ writer->account_idx ];
425 0 : memcpy( account, tbl, sizeof(fd_solcap_account_tbl_t) );
426 :
427 : /* Since we don't yet know the final position of the account table,
428 : we temporarily store a global offset. This will later get
429 : converted into a chunk offset. */
430 0 : account->acc_coff = (long)chunk_goff;
431 0 : }
432 :
433 : /* Serialize chunk header */
434 :
435 0 : ulong chunk_end_goff = FTELL_BAIL( writer->file );
436 :
437 0 : fd_solcap_chunk_t chunk = {
438 0 : .magic = FD_SOLCAP_V1_ACCT_MAGIC,
439 0 : .meta_coff = (uint)meta_coff,
440 0 : .meta_sz = (uint)stream.bytes_written,
441 0 : .total_sz = chunk_end_goff - chunk_goff
442 0 : };
443 :
444 : /* Write out chunk */
445 :
446 0 : FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET );
447 0 : FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file );
448 :
449 : /* Restore stream cursor */
450 :
451 0 : FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET );
452 :
453 : /* Wind up for next iteration */
454 :
455 0 : writer->account_idx += 1U;
456 :
457 0 : return 0;
458 0 : }
459 :
460 : void
461 : fd_solcap_writer_set_slot( fd_solcap_writer_t * writer,
462 0 : ulong slot ) {
463 :
464 0 : if( FD_LIKELY( !writer ) ) return;
465 :
466 : /* Discard account table buffer */
467 0 : writer->account_table_goff = 0UL;
468 0 : writer->account_idx = 0UL;
469 0 : writer->slot = slot;
470 0 : }
471 :
472 : int
473 : fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer,
474 : void const * bank_hash,
475 : void const * prev_bank_hash,
476 : void const * account_delta_hash,
477 : void const * poh_hash,
478 0 : ulong signature_cnt ) {
479 :
480 0 : if( FD_LIKELY( !writer ) ) return 0;
481 :
482 0 : fd_solcap_BankPreimage preimage_pb[1] = {{0}};
483 0 : preimage_pb->signature_cnt = signature_cnt;
484 0 : preimage_pb->account_cnt = writer->account_idx;
485 0 : memcpy( preimage_pb->bank_hash, bank_hash, 32UL );
486 0 : memcpy( preimage_pb->prev_bank_hash, prev_bank_hash, 32UL );
487 0 : memcpy( preimage_pb->account_delta_hash, account_delta_hash, 32UL );
488 0 : memcpy( preimage_pb->poh_hash, poh_hash, 32UL );
489 :
490 0 : return fd_solcap_write_bank_preimage2( writer, preimage_pb );
491 0 : }
492 :
493 :
494 : int
495 : fd_solcap_write_bank_preimage2( fd_solcap_writer_t * writer,
496 0 : fd_solcap_BankPreimage * preimage_pb ) {
497 :
498 0 : if( FD_LIKELY( !writer ) ) return 0;
499 :
500 0 : int err = fd_solcap_flush_account_table( writer );
501 0 : if( FD_UNLIKELY( err!=0 ) ) return err;
502 :
503 : /* Leave space for header */
504 :
505 0 : ulong chunk_goff = FTELL_BAIL( writer->file );
506 0 : FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) );
507 :
508 : /* Fixup predefined entries */
509 :
510 0 : preimage_pb->slot = writer->slot;
511 0 : if( writer->account_table_goff ) {
512 0 : preimage_pb->account_cnt = writer->account_idx;
513 0 : preimage_pb->account_table_coff = (long)writer->account_table_goff - (long)chunk_goff;
514 0 : }
515 :
516 : /* Serialize bank preimage */
517 :
518 0 : uchar preimage_pb_enc[ FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT ] = {0};
519 0 : pb_ostream_t stream = pb_ostream_from_buffer( preimage_pb_enc, sizeof(preimage_pb_enc) );
520 0 : FD_TEST( pb_encode( &stream, fd_solcap_BankPreimage_fields, preimage_pb ) );
521 0 : ulong meta_sz = stream.bytes_written;
522 :
523 0 : FWRITE_BAIL( preimage_pb_enc, 1UL, meta_sz, writer->file );
524 0 : FALIGN_BAIL( writer->file, 8UL );
525 0 : ulong chunk_end_goff = FTELL_BAIL( writer->file );
526 :
527 : /* Serialize chunk header */
528 :
529 0 : fd_solcap_chunk_t chunk = {
530 0 : .magic = FD_SOLCAP_V1_BANK_MAGIC,
531 0 : .meta_coff = (uint)sizeof(fd_solcap_chunk_t),
532 0 : .meta_sz = (uint)meta_sz,
533 0 : .total_sz = chunk_end_goff - chunk_goff
534 0 : };
535 :
536 : /* Write out chunk */
537 :
538 0 : FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET );
539 0 : FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file );
540 :
541 : /* Restore stream cursor */
542 :
543 0 : FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET );
544 :
545 0 : return 0;
546 0 : }
547 :
548 : int fd_solcap_write_transaction2( fd_solcap_writer_t * writer,
549 0 : fd_solcap_Transaction * txn ) {
550 :
551 0 : if( FD_LIKELY( !writer ) ) return 0;
552 :
553 : /* Locate chunk */
554 0 : ulong chunk_goff = FTELL_BAIL( writer->file );
555 0 : FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) );
556 :
557 : /* Serialize and write transaction */
558 0 : uchar txn_pb_enc[ FD_SOLCAP_TRANSACTION_FOOTPRINT ];
559 0 : pb_ostream_t stream = pb_ostream_from_buffer( txn_pb_enc, sizeof(txn_pb_enc) );
560 0 : FD_TEST( pb_encode( &stream, fd_solcap_Transaction_fields, txn ) );
561 :
562 0 : FWRITE_BAIL( txn_pb_enc, 1UL, stream.bytes_written, writer->file );
563 0 : FALIGN_BAIL( writer->file, 8UL );
564 0 : ulong chunk_end_goff = FTELL_BAIL( writer->file );
565 :
566 : /* Serialize chunk header */
567 0 : fd_solcap_chunk_t chunk = {
568 0 : .magic = FD_SOLCAP_V1_TRXN_MAGIC,
569 0 : .meta_coff = (uint)sizeof(fd_solcap_chunk_t),
570 0 : .meta_sz = (uint)stream.bytes_written,
571 0 : .total_sz = chunk_end_goff - chunk_goff
572 0 : };
573 :
574 : /* Write out chunk */
575 0 : FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET );
576 0 : FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file );
577 :
578 : /* Restore stream cursor */
579 :
580 0 : FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET );
581 :
582 0 : return 0;
583 0 : }
|