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