Line data Source code
1 : #include "../fd_flamenco.h"
2 : #include "fd_solcap_proto.h"
3 : #include "fd_solcap_reader.h"
4 : #include "fd_solcap.pb.h"
5 : #include "../../ballet/base58/fd_base58.h"
6 : #include "../types/fd_types.h"
7 : #include "../types/fd_types_yaml.h"
8 : #include "../../ballet/nanopb/pb_decode.h"
9 :
10 : #include <errno.h>
11 : #include <stdio.h>
12 : #include <sys/stat.h> /* mkdir(2) */
13 : #include <fcntl.h> /* open(2) */
14 : #include <unistd.h> /* close(2) */
15 :
16 : /* TODO: Ugly -- These should not be hard coded! */
17 0 : #define SOLCAP_FILE_NAME_LEN (13UL)
18 0 : #define SOLCAP_SUFFIX_LEN (7UL) /* .solcap */
19 :
20 : #if FD_USING_GCC && __GNUC__ >= 15
21 : #pragma GCC diagnostic ignored "-Wunterminated-string-initialization"
22 : #endif
23 :
24 : static const uchar
25 : _vote_program_address[ 32 ] =
26 : "\x07\x61\x48\x1d\x35\x74\x74\xbb\x7c\x4d\x76\x24\xeb\xd3\xbd\xb3"
27 : "\xd8\x35\x5e\x73\xd1\x10\x43\xfc\x0d\xa3\x53\x80\x00\x00\x00\x00";
28 :
29 : static const uchar
30 : _stake_program_address[ 32 ] =
31 : "\x06\xa1\xd8\x17\x91\x37\x54\x2a\x98\x34\x37\xbd\xfe\x2a\x7a\xb2"
32 : "\x55\x7f\x53\x5c\x8a\x78\x72\x2b\x68\xa4\x9d\xc0\x00\x00\x00\x00";
33 :
34 : static void
35 0 : normalize_filename( const char * original_str, char * file_name, char prefix ) {
36 : /* We either need to truncate if too long or pad if too short (16 chars) */
37 :
38 0 : file_name[0] = prefix;
39 0 : ulong original_str_len = strlen( original_str ) - SOLCAP_SUFFIX_LEN + 1;
40 0 : if ( original_str_len <= SOLCAP_FILE_NAME_LEN ) {
41 0 : fd_memcpy( file_name + 1, original_str, original_str_len );
42 0 : for ( ulong i = original_str_len; i < SOLCAP_FILE_NAME_LEN; i++ ) {
43 0 : file_name[ i ] = ' ';
44 0 : }
45 0 : }
46 0 : else {
47 0 : ulong start_idx = original_str_len - SOLCAP_FILE_NAME_LEN;
48 0 : fd_memcpy( file_name + 1, original_str + start_idx, SOLCAP_FILE_NAME_LEN );
49 :
50 0 : }
51 0 : file_name[ SOLCAP_FILE_NAME_LEN ] = '\0';
52 0 : }
53 :
54 : /* Define routines for sorting the bank hash account delta accounts.
55 : The solcap format does not mandate accounts to be sorted. */
56 :
57 : static inline int
58 : fd_solcap_account_tbl_lt( fd_solcap_account_tbl_t const * a,
59 0 : fd_solcap_account_tbl_t const * b ) {
60 0 : return memcmp( a->key, b->key, 32UL ) < 0;
61 0 : }
62 : #define SORT_NAME sort_account_tbl
63 : #define SORT_KEY_T fd_solcap_account_tbl_t
64 : #define SORT_BEFORE(a,b) fd_solcap_account_tbl_lt( &(a), &(b) )
65 : #include "../../util/tmpl/fd_sort.c"
66 : struct fd_solcap_differ {
67 : fd_solcap_chunk_iter_t iter [2];
68 : fd_solcap_BankPreimage preimage[2];
69 :
70 : int verbose;
71 : int dump_dir_fd;
72 : char const * dump_dir;
73 : char const * file_paths[2];
74 : };
75 :
76 : typedef struct fd_solcap_differ fd_solcap_differ_t;
77 :
78 : struct fd_solcap_txn_differ {
79 : FILE * file[2];
80 : fd_solcap_chunk_iter_t iter[2];
81 : long chunk_gaddr[2];
82 : fd_solcap_Transaction transaction[2];
83 : uchar meta_buf[128][2];
84 : };
85 :
86 : typedef struct fd_solcap_txn_differ fd_solcap_txn_differ_t;
87 :
88 : static fd_solcap_differ_t *
89 : fd_solcap_differ_new( fd_solcap_differ_t * diff,
90 : FILE * streams[2],
91 0 : const char * cap_path[2] ) {
92 :
93 : /* Attach to capture files */
94 :
95 0 : for( ulong i=0UL; i<2UL; i++ ) {
96 0 : FILE * stream = streams[i];
97 :
98 : /* Set file names */
99 0 : diff->file_paths[i] = cap_path[i];
100 :
101 : /* Read file header */
102 0 : fd_solcap_fhdr_t hdr[1];
103 0 : if( FD_UNLIKELY( 1UL!=fread( hdr, sizeof(fd_solcap_fhdr_t), 1UL, stream ) ) ) {
104 0 : FD_LOG_WARNING(( "Failed to read file=%s header (%d-%s)",
105 0 : diff->file_paths[i], errno, strerror( errno ) ));
106 0 : return NULL;
107 0 : }
108 :
109 : /* Seek to first chunk */
110 0 : long skip = ( (long)hdr->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t) );
111 0 : if( FD_UNLIKELY( 0!=fseek( stream, skip, SEEK_CUR ) ) ) {
112 0 : FD_LOG_WARNING(( "Failed to seek to first chunk (%d-%s)", errno, strerror( errno ) ));
113 0 : return NULL;
114 0 : }
115 :
116 0 : if( FD_UNLIKELY( !fd_solcap_chunk_iter_new( &diff->iter[i], stream ) ) )
117 0 : FD_LOG_CRIT(( "fd_solcap_chunk_iter_new() failed" ));
118 0 : }
119 :
120 0 : return diff;
121 0 : }
122 :
123 : /* fd_solcap_differ_advance seeks an iterator to the next bank hash.
124 : idx identifies the iterator. Returns 1 on success, 0 if end-of-file
125 : reached, and negated errno-like on failure. */
126 :
127 : static int
128 : fd_solcap_differ_advance( fd_solcap_differ_t * diff,
129 0 : ulong idx ) { /* [0,2) */
130 :
131 0 : fd_solcap_chunk_iter_t * iter = &diff->iter [ idx ];
132 0 : fd_solcap_BankPreimage * preimage = &diff->preimage[ idx ];
133 :
134 0 : long off = fd_solcap_chunk_iter_find( iter, FD_SOLCAP_V1_BANK_MAGIC );
135 0 : if( FD_UNLIKELY( off<0L ) )
136 0 : return fd_solcap_chunk_iter_err( iter );
137 :
138 0 : int err = fd_solcap_read_bank_preimage( iter->stream, iter->chunk_off, preimage, &iter->chunk );
139 0 : if( FD_UNLIKELY( err!=0 ) ) return -err;
140 0 : return 1;
141 0 : }
142 :
143 : /* fd_solcap_differ_sync synchronizes the given two iterators such that
144 : both point to the lowest common slot number. Returns 1 on success
145 : and 0 if no common slot was found. Negative values are negated
146 : errno-like. */
147 :
148 : static int
149 0 : fd_solcap_differ_sync( fd_solcap_differ_t * diff, ulong start_slot, ulong end_slot ) {
150 :
151 : /* Seek to first bank preimage object */
152 :
153 0 : for( ulong i=0UL; i<2UL; i++ ) {
154 0 : int res = fd_solcap_differ_advance( diff, i );
155 0 : if( FD_UNLIKELY( res!=1 ) ) return res;
156 0 : }
157 :
158 0 : ulong prev_slot0 = diff->preimage[ 0 ].slot;
159 0 : ulong prev_slot1 = diff->preimage[ 1 ].slot;
160 :
161 0 : for(;;) {
162 0 : ulong slot0 = diff->preimage[ 0 ].slot;
163 0 : ulong slot1 = diff->preimage[ 1 ].slot;
164 :
165 : /* Handle cases where slot is skipped in one or the other */
166 0 : if ( FD_UNLIKELY( prev_slot0 < slot1 && slot0 > slot1 ) ) {
167 0 : FD_LOG_WARNING(("Slot range (%lu,%lu) skipped in file=%s\n",
168 0 : prev_slot0, slot0, diff->file_paths[0]));
169 0 : }
170 0 : else if ( FD_UNLIKELY( prev_slot1 < slot0 && slot1 > slot0 ) ) {
171 0 : FD_LOG_WARNING(("Slot range (%lu,%lu) skipped in file=%s\n",
172 0 : prev_slot1, slot1, diff->file_paths[1]));
173 0 : }
174 :
175 0 : if( slot0 == slot1 ) {
176 0 : if ( slot0 < start_slot ) {
177 0 : int res;
178 0 : res = fd_solcap_differ_advance( diff, 0 );
179 0 : if( FD_UNLIKELY( res <= 0 ) ) return res;
180 0 : res = fd_solcap_differ_advance( diff, 1 );
181 0 : if( FD_UNLIKELY( res <= 0 ) ) return res;
182 0 : }
183 0 : else if ( slot0 > end_slot ) {
184 0 : return 0;
185 0 : }
186 0 : else {
187 0 : return 1;
188 0 : }
189 0 : }
190 0 : else {
191 0 : ulong idx = slot0>slot1;
192 0 : int res = fd_solcap_differ_advance( diff, idx );
193 0 : if( FD_UNLIKELY( res<=0 ) ) return res;
194 0 : }
195 0 : prev_slot0 = slot0;
196 0 : prev_slot1 = slot1;
197 0 : }
198 :
199 0 : return 0;
200 0 : }
201 :
202 : static int
203 : fd_solcap_can_pretty_print( uchar const owner [ static 32 ],
204 0 : uchar const pubkey[ static 32 ] ) {
205 :
206 : /* TODO clean up */
207 0 : uchar _sysvar_clock[ 32 ];
208 0 : fd_base58_decode_32( "SysvarC1ock11111111111111111111111111111111", _sysvar_clock );
209 0 : uchar _sysvar_rent[ 32 ];
210 0 : fd_base58_decode_32( "SysvarRent111111111111111111111111111111111", _sysvar_rent );
211 0 : uchar _sysvar_epoch_rewards[ 32 ];
212 0 : fd_base58_decode_32( "SysvarEpochRewards1111111111111111111111111", _sysvar_epoch_rewards );
213 0 : uchar _sysvar_stake_history[ 32 ];
214 0 : fd_base58_decode_32( "SysvarStakeHistory1111111111111111111111111", _sysvar_stake_history );
215 :
216 0 : if( 0==memcmp( owner, _vote_program_address, 32UL ) )
217 0 : return 1;
218 0 : if( 0==memcmp( owner, _stake_program_address, 32UL ) )
219 0 : return 1;
220 :
221 0 : if( 0==memcmp( pubkey, _sysvar_clock, 32UL ) )
222 0 : return 1;
223 0 : if( 0==memcmp( pubkey, _sysvar_rent, 32UL ) )
224 0 : return 1;
225 0 : if( 0==memcmp( pubkey, _sysvar_epoch_rewards, 32UL ) )
226 0 : return 1;
227 0 : if( 0==memcmp( pubkey, _sysvar_stake_history, 32UL ) )
228 0 : return 1;
229 0 : return 0;
230 0 : }
231 :
232 : static int
233 : fd_solcap_account_pretty_print( uchar const pubkey[ static 32 ],
234 : uchar const owner[ static 32 ],
235 : uchar const * data,
236 : ulong data_sz,
237 0 : FILE * file ) {
238 :
239 0 : FD_SCRATCH_SCOPE_BEGIN {
240 :
241 0 : fd_flamenco_yaml_t * yaml =
242 0 : fd_flamenco_yaml_init( fd_flamenco_yaml_new(
243 0 : fd_scratch_alloc( fd_flamenco_yaml_align(), fd_flamenco_yaml_footprint() ) ),
244 0 : file );
245 0 : FD_TEST( yaml );
246 :
247 : /* TODO clean up */
248 0 : uchar _sysvar_clock[ 32 ];
249 0 : fd_base58_decode_32( "SysvarC1ock11111111111111111111111111111111", _sysvar_clock );
250 0 : uchar _sysvar_rent[ 32 ];
251 0 : fd_base58_decode_32( "SysvarRent111111111111111111111111111111111", _sysvar_rent );
252 0 : uchar _sysvar_epoch_rewards[ 32 ];
253 0 : fd_base58_decode_32( "SysvarEpochRewards1111111111111111111111111", _sysvar_epoch_rewards );
254 0 : uchar _sysvar_stake_history[ 32 ];
255 0 : fd_base58_decode_32( "SysvarStakeHistory1111111111111111111111111", _sysvar_stake_history );
256 :
257 0 : if( 0==memcmp( owner, _vote_program_address, 32UL ) ) {
258 0 : fd_vote_state_versioned_t * vote_state = fd_bincode_decode_scratch( vote_state_versioned, data, data_sz, NULL );
259 0 : if( FD_UNLIKELY( !vote_state ) ) return -ENOMEM;
260 0 : fd_vote_state_versioned_walk( yaml, vote_state, fd_flamenco_yaml_walk, NULL, 0U, 0U );
261 0 : } else if( 0==memcmp( owner, _stake_program_address, 32UL ) ) {
262 0 : fd_stake_state_v2_t * stake_state = fd_bincode_decode_scratch( stake_state_v2, data, data_sz, NULL );
263 0 : if( FD_UNLIKELY( !stake_state ) ) return -ENOMEM;
264 0 : fd_stake_state_v2_walk( yaml, stake_state, fd_flamenco_yaml_walk, NULL, 0U, 0U );
265 0 : } else if( 0==memcmp( pubkey, _sysvar_clock, 32UL ) ) {
266 0 : fd_sol_sysvar_clock_t * clock = fd_bincode_decode_scratch( sol_sysvar_clock, data, data_sz, NULL );
267 0 : if( FD_UNLIKELY( !clock ) ) return -ENOMEM;
268 0 : fd_sol_sysvar_clock_walk( yaml, clock, fd_flamenco_yaml_walk, NULL, 0U, 0U );
269 0 : } else if( 0==memcmp( pubkey, _sysvar_rent, 32UL ) ) {
270 0 : fd_rent_t * rent = fd_bincode_decode_scratch( rent, data, data_sz, NULL );
271 0 : if( FD_UNLIKELY( !rent ) ) return -ENOMEM;
272 0 : fd_rent_walk( yaml, rent, fd_flamenco_yaml_walk, NULL, 0U, 0U );
273 0 : } else if( 0==memcmp( pubkey, _sysvar_epoch_rewards, 32UL ) ) {
274 0 : fd_sysvar_epoch_rewards_t * epoch_rewards = fd_bincode_decode_scratch( sysvar_epoch_rewards, data, data_sz, NULL );
275 0 : if( FD_UNLIKELY( !epoch_rewards ) ) return -ENOMEM;
276 0 : fd_sysvar_epoch_rewards_walk( yaml, epoch_rewards, fd_flamenco_yaml_walk, NULL, 0U, 0U );
277 0 : } else if( 0==memcmp( pubkey, _sysvar_stake_history, 32UL ) ) {
278 0 : fd_stake_history_t * stake_history = fd_bincode_decode_scratch( stake_history, data, data_sz, NULL );
279 0 : if( FD_UNLIKELY( !stake_history ) ) return -ENOMEM;
280 0 : fd_stake_history_walk( yaml, stake_history, fd_flamenco_yaml_walk, NULL, 0U, 0U );
281 0 : }
282 :
283 0 : int err = ferror( file );
284 0 : if( FD_UNLIKELY( err!=0 ) ) return err;
285 :
286 : /* No need to destroy structures, using fd_scratch allocator */
287 :
288 0 : fd_flamenco_yaml_delete( yaml );
289 0 : return 0;
290 0 : } FD_SCRATCH_SCOPE_END;
291 0 : }
292 :
293 : /* fd_solcap_dump_account_data writes a binary file containing exactly
294 : the given account's content. */
295 :
296 : static void
297 : fd_solcap_dump_account_data( fd_solcap_differ_t * diff,
298 : fd_solcap_AccountMeta const * meta,
299 : char const * filename,
300 0 : void const * acc_data ) {
301 : /* Create dump file */
302 0 : int fd = openat( diff->dump_dir_fd, filename, O_CREAT|O_WRONLY|O_TRUNC, 0666 );
303 0 : if( FD_UNLIKELY( fd<0 ) )
304 0 : FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)",
305 0 : diff->dump_dir_fd, filename, errno, strerror( errno ) ));
306 :
307 : /* Write dump file */
308 0 : FILE * file = fdopen( fd, "wb" );
309 0 : FD_TEST( meta->data_sz == fwrite( acc_data, 1UL, meta->data_sz, file ) );
310 0 : fclose( file ); /* Closes fd */
311 0 : }
312 :
313 : static void
314 : fd_solcap_diff_account_data( fd_solcap_differ_t * diff,
315 : fd_solcap_AccountMeta const meta [ static 2 ],
316 : fd_solcap_account_tbl_t const * const entry [ static 2 ],
317 0 : ulong const data_goff[ static 2 ] ) {\
318 0 :
319 : /* Streaming diff */
320 0 : int data_eq = meta[0].data_sz == meta[1].data_sz;
321 0 : if( data_eq ) {
322 0 : for( ulong i=0UL; i<2UL; i++ ) {
323 0 : if ( data_goff[i] == ULONG_MAX ) {
324 0 : continue;
325 0 : }
326 0 : FD_TEST( 0==fseek( diff->iter[ i ].stream, (long)data_goff[i], SEEK_SET ) );
327 0 : }
328 :
329 0 : for( ulong off=0UL; off<meta[0].data_sz; ) {
330 0 : # define BUFSZ (512UL)
331 :
332 : /* Read chunks */
333 0 : uchar buf[2][ BUFSZ ];
334 0 : ulong sz = fd_ulong_min( BUFSZ, meta[0].data_sz-off );
335 0 : for( ulong i=0UL; i<2UL; i++ )
336 0 : FD_TEST( sz==fread( &buf[i], 1UL, sz, diff->iter[i].stream ) );
337 :
338 : /* Compare chunks */
339 0 : data_eq = 0==memcmp( buf[0], buf[1], sz );
340 0 : if( !data_eq ) break;
341 :
342 0 : off += sz;
343 0 : # undef BUFSZ
344 0 : }
345 0 : }
346 0 : if( data_eq ) return;
347 :
348 : /* Dump account data to file */
349 0 : if( diff->verbose >= 4 ) {
350 :
351 : /* TODO: Remove hardcoded account size check */
352 0 : FD_TEST( meta[0].data_sz <= 1048576 );
353 0 : FD_TEST( meta[1].data_sz <= 1048576 );
354 :
355 0 : FD_SCRATCH_SCOPE_BEGIN {
356 0 : void * acc_data[2];
357 0 : acc_data[0] = fd_scratch_alloc( 1UL, meta[0].data_sz );
358 0 : acc_data[1] = fd_scratch_alloc( 1UL, meta[1].data_sz );
359 0 : fd_memset( acc_data[0], 0, meta[0].data_sz );
360 0 : fd_memset( acc_data[1], 0, meta[1].data_sz );
361 :
362 0 : for( ulong i=0UL; i<2UL; i++ ) {
363 0 : if ( data_goff[i] == ULONG_MAX ) {
364 0 : continue;
365 0 : }
366 :
367 : /* Rewind capture stream */
368 0 : FD_TEST( 0==fseek( diff->iter[ i ].stream, (long)data_goff[i], SEEK_SET ) );
369 :
370 : /* Copy data */
371 0 : FD_TEST( meta[i].data_sz == fread( acc_data[i], 1UL, meta[i].data_sz, diff->iter[i].stream ) );
372 0 : }
373 :
374 0 : for( ulong i=0; i<2; i++ ) {
375 0 : char filename[ FD_BASE58_ENCODED_32_LEN+2+4+1 ];
376 0 : int res = snprintf( filename, sizeof(filename), "%s.%lu.bin",
377 0 : FD_BASE58_ENC_32_ALLOCA( entry[i]->key ), i );
378 0 : FD_TEST( (res>0) & (res<(int)sizeof(filename)) );
379 0 : fd_solcap_dump_account_data( diff, meta+i, filename, acc_data[i] );
380 0 : }
381 :
382 : /* Inform user */
383 0 : printf( " (%s) data: %s/%s.0.bin\n"
384 0 : " (%s) data: %s/%s.1.bin\n"
385 0 : " vimdiff <(xxd '%s/%s.bin') <(xxd '%s/%s.bin')\n",
386 0 : diff->file_paths[0], diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ),
387 0 : diff->file_paths[1], diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ),
388 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ),
389 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ) );
390 :
391 0 : if( fd_solcap_can_pretty_print( meta[0].owner, entry[0]->key )
392 0 : & fd_solcap_can_pretty_print( meta[1].owner, entry[1]->key ) ) {
393 :
394 0 : for( ulong i=0UL; i<2UL; i++ ) {
395 : /* Create YAML file */
396 0 : char path[ FD_BASE58_ENCODED_32_LEN+2+4+1 ];
397 0 : int res = snprintf( path, sizeof(path), "%s.%lu.yml", FD_BASE58_ENC_32_ALLOCA( entry[i]->key ), i );
398 0 : FD_TEST( (res>0) & (res<(int)sizeof(path)) );
399 0 : int fd = openat( diff->dump_dir_fd, path, O_CREAT|O_WRONLY|O_TRUNC, 0666 );
400 0 : if( FD_UNLIKELY( fd<0 ) )
401 0 : FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)",
402 0 : diff->dump_dir_fd, path, errno, strerror( errno ) ));
403 :
404 : /* Write YAML file */
405 0 : FILE * file = fdopen( fd, "wb" );
406 0 : fd_solcap_account_pretty_print( entry[i]->key, meta[i].owner, acc_data[i], meta[i].data_sz, file );
407 0 : fclose( file ); /* closes fd */
408 0 : }
409 :
410 :
411 : /* Inform user */
412 0 : printf( " vimdiff '%s/%s.0.yml' '%s/%s.1.yml'\n",
413 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ),
414 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ) );
415 :
416 0 : }
417 0 : } FD_SCRATCH_SCOPE_END;
418 0 : }
419 0 : }
420 :
421 : /* fd_solcap_diff_account prints further details about a mismatch
422 : between two accounts. Preserves stream cursors. */
423 :
424 : static void
425 : fd_solcap_diff_account( fd_solcap_differ_t * diff,
426 : fd_solcap_account_tbl_t const * const entry [ static 2 ],
427 0 : ulong const acc_tbl_goff[ static 2 ] ) {
428 :
429 : /* Remember current file offsets (should probably just use readat) */
430 0 : long orig_off[ 2 ];
431 0 : for( ulong i=0UL; i<2UL; i++ ) {
432 0 : orig_off[ i ] = ftell( diff->iter[ i ].stream );
433 0 : if( FD_UNLIKELY( orig_off[ i ]<0L ) )
434 0 : FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) ));
435 0 : }
436 :
437 : /* Read account meta */
438 0 : fd_solcap_AccountMeta meta[2];
439 0 : ulong data_goff[2] = {ULONG_MAX, ULONG_MAX};
440 0 : for( ulong i=0UL; i<2UL; i++ ) {
441 0 : FILE * stream = diff->iter[ i ].stream;
442 0 : int err = fd_solcap_find_account( stream, meta+i, &data_goff[i], entry[i], acc_tbl_goff[i] );
443 0 : FD_TEST( err==0 );
444 0 : }
445 0 : if( 0!=memcmp( meta[0].owner, meta[1].owner, 32UL ) )
446 0 : printf( "%s owner: %s\n"
447 0 : "%s owner: %s\n",
448 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( meta[0].owner ),
449 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( meta[1].owner ) );
450 0 : if( meta[0].lamports != meta[1].lamports )
451 0 : printf( " (%s) lamports: %lu\n"
452 0 : " (%s) lamports: %lu\n",
453 0 : diff->file_paths[0], meta[0].lamports,
454 0 : diff->file_paths[1], meta[1].lamports );
455 0 : if( meta[0].data_sz != meta[1].data_sz )
456 0 : printf( " (%s) data_sz: %lu\n"
457 0 : " (%s) data_sz: %lu\n",
458 0 : diff->file_paths[0], meta[0].data_sz,
459 0 : diff->file_paths[1], meta[1].data_sz );
460 0 : if( meta[0].slot != meta[1].slot )
461 0 : printf( " (%s) slot: %lu\n"
462 0 : " (%s) slot: %lu\n",
463 0 : diff->file_paths[0], meta[0].slot,
464 0 : diff->file_paths[1], meta[1].slot );
465 0 : if( meta[0].executable != meta[1].executable )
466 0 : printf( " (%s) executable: %d\n"
467 0 : " (%s) executable: %d\n",
468 0 : diff->file_paths[0], meta[0].executable,
469 0 : diff->file_paths[1], meta[1].executable );
470 0 : if( ( (meta[0].data_sz != 0UL) | fd_solcap_includes_account_data( &meta[0] ) )
471 0 : | ( (meta[1].data_sz != 0UL) | fd_solcap_includes_account_data( &meta[1] ) ) )
472 0 : fd_solcap_diff_account_data( diff, meta, entry, data_goff );
473 :
474 : /* Restore file offsets */
475 0 : for( ulong i=0UL; i<2UL; i++ ) {
476 0 : if( FD_UNLIKELY( 0!=fseek( diff->iter[ i ].stream, orig_off[ i ], SEEK_SET ) ) )
477 0 : FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) ));
478 0 : }
479 0 : }
480 :
481 : /* fd_solcap_diff_missing_account is like fd_solcap_diff_account but in
482 : the case that either side of the account is missing entirely. */
483 :
484 : static void
485 : fd_solcap_diff_missing_account( fd_solcap_differ_t * diff,
486 : fd_solcap_account_tbl_t const * const entry,
487 : ulong const acc_tbl_goff,
488 0 : FILE * stream ) {
489 :
490 : /* Remember current file offset */
491 0 : long orig_off = ftell( stream );
492 0 : if( FD_UNLIKELY( orig_off<0L ) )
493 0 : FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) ));
494 :
495 : /* Read account meta */
496 0 : fd_solcap_AccountMeta meta[1];
497 0 : ulong data_goff[1];
498 0 : int err = fd_solcap_find_account( stream, meta, data_goff, entry, acc_tbl_goff );
499 0 : FD_TEST( err==0 );
500 :
501 0 : printf(
502 0 : " lamports: %lu\n"
503 0 : " data_sz: %lu\n"
504 0 : " owner: %s\n"
505 0 : " slot: %lu\n"
506 0 : " executable: %d\n",
507 0 : meta->lamports,
508 0 : meta->data_sz,
509 0 : FD_BASE58_ENC_32_ALLOCA( meta->owner ),
510 0 : meta->slot,
511 0 : meta->executable
512 0 : );
513 :
514 : /* Dump account data to file */
515 0 : if( diff->verbose >= 4 ) {
516 :
517 : /* TODO: Remove hardcoded account size check */
518 0 : FD_TEST( meta->data_sz <= 8388608 );
519 :
520 0 : FD_SCRATCH_SCOPE_BEGIN {
521 0 : void * acc_data = fd_scratch_alloc( 1UL, meta->data_sz );
522 :
523 : /* Rewind capture stream */
524 0 : FD_TEST( 0==fseek(stream, (long)*data_goff, SEEK_SET ) );
525 :
526 : /* Copy data */
527 0 : FD_TEST( meta->data_sz == fread( acc_data, 1UL, meta->data_sz, stream ) );
528 :
529 0 : char filename[ FD_BASE58_ENCODED_32_LEN+4+1 ];
530 0 : int res = snprintf( filename, sizeof(filename), "%s.bin", FD_BASE58_ENC_32_ALLOCA( entry->key ) );
531 0 : FD_TEST( (res>0) & (res<(int)sizeof(filename)) );
532 0 : fd_solcap_dump_account_data( diff, meta, filename, acc_data );
533 :
534 : /* Inform user */
535 0 : printf( " data: %s/%s.bin\n"
536 0 : " xxd '%s/%s.bin'\n"
537 0 : " explorer: 'https://explorer.solana.com/block/%lu?accountFilter=%s&filter=all'",
538 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ),
539 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ),
540 0 : meta->slot, FD_BASE58_ENC_32_ALLOCA( entry->key ) );
541 :
542 0 : #pragma GCC diagnostic push
543 0 : #pragma GCC diagnostic ignored "-Wnonnull"
544 0 : if( fd_solcap_can_pretty_print( meta->owner, entry->key ) ) {
545 0 : #pragma GCC diagnostic pop
546 : /* Create YAML file */
547 0 : char path[ FD_BASE58_ENCODED_32_LEN+4+1 ];
548 0 : int res = snprintf( path, sizeof(path), "%s.yml", FD_BASE58_ENC_32_ALLOCA( entry->key ) );
549 0 : FD_TEST( (res>0) & (res<(int)sizeof(path)) );
550 0 : int fd = openat( diff->dump_dir_fd, path, O_CREAT|O_WRONLY|O_TRUNC, 0666 );
551 0 : if( FD_UNLIKELY( fd<0 ) )
552 0 : FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)",
553 0 : diff->dump_dir_fd, path, errno, strerror( errno ) ));
554 :
555 : /* Write YAML file */
556 0 : FILE * file = fdopen( fd, "wb" );
557 0 : fd_solcap_account_pretty_print( entry->key, meta->owner, acc_data, meta->data_sz, file );
558 0 : fclose( file ); /* closes fd */
559 :
560 : /* Inform user */
561 0 : printf( " cat '%s/%s.yml'\n",
562 0 : diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ) );
563 0 : }
564 0 : } FD_SCRATCH_SCOPE_END;
565 0 : }
566 0 : }
567 :
568 : /* fd_solcap_diff_account_tbl detects and prints differences in the
569 : accounts that were hashed into the account delta hash. */
570 :
571 : static int
572 0 : fd_solcap_diff_account_tbl( fd_solcap_differ_t * diff ) {
573 0 : int has_mismatch = 0;
574 :
575 : /* Read and sort tables */
576 :
577 0 : fd_solcap_account_tbl_t * tbl [2];
578 0 : fd_solcap_account_tbl_t * tbl_end[2];
579 0 : ulong chunk_goff[2];
580 0 : for( ulong i=0UL; i<2UL; i++ ) {
581 0 : if( diff->preimage[i].account_table_coff == 0L ) {
582 0 : FD_LOG_WARNING(( "Missing accounts table in capture" ));
583 0 : return 1;
584 0 : }
585 0 : chunk_goff[i] = (ulong)( (long)diff->iter[i].chunk_off + diff->preimage[i].account_table_coff );
586 :
587 : /* Read table meta and seek to table */
588 0 : FILE * stream = diff->iter[i].stream;
589 0 : fd_solcap_AccountTableMeta meta[1];
590 0 : int err = fd_solcap_find_account_table( stream, meta, chunk_goff[i] );
591 0 : FD_TEST( err==0 );
592 :
593 0 : if( FD_UNLIKELY( meta->account_table_cnt > INT_MAX ) ) {
594 0 : FD_LOG_WARNING(( "Too many accounts in capture" ));
595 0 : return 1;
596 0 : }
597 :
598 : /* Allocate table */
599 0 : ulong tbl_cnt = meta->account_table_cnt;
600 0 : ulong tbl_align = alignof(fd_solcap_account_tbl_t);
601 0 : ulong tbl_sz = tbl_cnt * sizeof(fd_solcap_account_tbl_t);
602 0 : FD_TEST( fd_scratch_alloc_is_safe( tbl_align, tbl_sz ) );
603 0 : tbl [i] = fd_scratch_alloc( tbl_align, tbl_sz );
604 0 : tbl_end[i] = tbl[i] + tbl_cnt;
605 :
606 : /* Read table */
607 0 : FD_TEST( tbl_cnt==fread( tbl[i], sizeof(fd_solcap_account_tbl_t), tbl_cnt, stream ) );
608 0 : }
609 :
610 0 : typedef struct {
611 0 : fd_solcap_account_tbl_t const * entry;
612 0 : int is_last_occurrence;
613 0 : } account_entry_info_t;
614 :
615 0 : account_entry_info_t * entries[2];
616 0 : ulong tbl_cnt[2];
617 0 : for( ulong i=0UL; i<2UL; i++ ) {
618 0 : tbl_cnt[i] = (ulong)(tbl_end[i] - tbl[i]);
619 0 : entries[i] = fd_scratch_alloc( alignof(account_entry_info_t), tbl_cnt[i] * sizeof(account_entry_info_t) );
620 :
621 0 : for( ulong j=0UL; j < tbl_cnt[i]; j++ ) {
622 0 : entries[i][j].entry = &tbl[i][j];
623 0 : entries[i][j].is_last_occurrence = 1;
624 0 : }
625 :
626 0 : for( ulong j=0UL; j < tbl_cnt[i]; j++ ) {
627 0 : for( ulong k=j+1UL; k < tbl_cnt[i]; k++ ) {
628 0 : if( memcmp(entries[i][j].entry->key, entries[i][k].entry->key, sizeof(entries[i][j].entry->key)) == 0 ) {
629 0 : entries[i][j].is_last_occurrence = 0;
630 0 : break;
631 0 : }
632 0 : }
633 0 : }
634 0 : }
635 :
636 0 : for( ulong i=0UL; i<2UL; i++ ) {
637 0 : for( ulong j=0UL; j < tbl_cnt[i]-1UL; j++ ) {
638 0 : for( ulong k=j+1UL; k < tbl_cnt[i]; k++ ) {
639 0 : if( memcmp(entries[i][j].entry->key, entries[i][k].entry->key, sizeof(entries[i][j].entry->key)) > 0 ) {
640 0 : account_entry_info_t temp = entries[i][j];
641 0 : entries[i][j] = entries[i][k];
642 0 : entries[i][k] = temp;
643 0 : }
644 0 : }
645 0 : }
646 0 : }
647 :
648 : /* Walk tables in parallel */
649 0 : ulong idx[2] = {0UL, 0UL};
650 :
651 0 : for(;;) {
652 0 : while( idx[0] < tbl_cnt[0] && !entries[0][idx[0]].is_last_occurrence ) idx[0]++;
653 0 : while( idx[1] < tbl_cnt[1] && !entries[1][idx[1]].is_last_occurrence ) idx[1]++;
654 :
655 0 : if( idx[0] >= tbl_cnt[0] ) break;
656 0 : if( idx[1] >= tbl_cnt[1] ) break;
657 :
658 0 : fd_solcap_account_tbl_t const * a = entries[0][idx[0]].entry;
659 0 : fd_solcap_account_tbl_t const * b = entries[1][idx[1]].entry;
660 :
661 0 : int key_cmp = memcmp( a->key, b->key, 32UL );
662 0 : if( key_cmp==0 ) {
663 0 : if( diff->verbose >= 3 ) {
664 0 : fd_solcap_account_tbl_t const * const entry_pair[2] = {a, b};
665 0 : fd_solcap_diff_account( diff, entry_pair, chunk_goff );
666 0 : }
667 0 : idx[0]++;
668 0 : idx[1]++;
669 0 : continue;
670 0 : }
671 :
672 0 : if( key_cmp<0 ) {
673 0 : has_mismatch = 1;
674 0 : printf( "\n (%s) account: %s\n", diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( a->key ) );
675 0 : if( diff->verbose >= 3 )
676 0 : fd_solcap_diff_missing_account( diff, a, chunk_goff[0], diff->iter[0].stream );
677 0 : idx[0]++;
678 0 : continue;
679 0 : }
680 :
681 0 : if( key_cmp>0 ) {
682 0 : has_mismatch = 1;
683 0 : printf( "\n (%s) account: %s\n", diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( b->key ) );
684 0 : if( diff->verbose >= 3 )
685 0 : fd_solcap_diff_missing_account( diff, b, chunk_goff[1], diff->iter[1].stream );
686 0 : idx[1]++;
687 0 : continue;
688 0 : }
689 0 : }
690 :
691 0 : while( idx[0] < tbl_cnt[0] ) {
692 0 : has_mismatch = 1;
693 0 : if( entries[0][idx[0]].is_last_occurrence ) {
694 0 : printf( "\n (%s) account: %s\n", diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( entries[0][idx[0]].entry->key ) );
695 0 : if( diff->verbose >= 3 )
696 0 : fd_solcap_diff_missing_account( diff, entries[0][idx[0]].entry, chunk_goff[0], diff->iter[0].stream );
697 0 : }
698 0 : idx[0]++;
699 0 : }
700 0 : while( idx[1] < tbl_cnt[1] ) {
701 0 : has_mismatch = 1;
702 0 : if( entries[1][idx[1]].is_last_occurrence ) {
703 0 : printf( "\n (%s) account: %s\n", diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( entries[1][idx[1]].entry->key ) );
704 0 : if( diff->verbose >= 3 )
705 0 : fd_solcap_diff_missing_account( diff, entries[1][idx[1]].entry, chunk_goff[1], diff->iter[1].stream );
706 0 : }
707 0 : idx[1]++;
708 0 : }
709 :
710 0 : return has_mismatch;
711 0 : }
712 :
713 : /* fd_solcap_diff_bank detects bank hash mismatches and prints a
714 : human-readable description of the root cause to stdout. Returns 0
715 : if bank hashes match, 1 if a mismatch was detected. */
716 :
717 : static int
718 0 : fd_solcap_diff_bank( fd_solcap_differ_t * diff ) {
719 :
720 0 : fd_solcap_BankPreimage const * pre = diff->preimage;
721 :
722 0 : FD_TEST( pre[0].slot == pre[1].slot );
723 :
724 0 : int has_bank_hash_mismatch = 0!=memcmp( pre[0].bank_hash, pre[1].bank_hash, 32UL );
725 :
726 0 : int has_any_mismatch = has_bank_hash_mismatch;
727 0 : if( 0!=memcmp( pre[0].prev_bank_hash, pre[1].prev_bank_hash, 32UL ) ) has_any_mismatch = 1;
728 0 : if( 0!=memcmp( pre[0].account_delta_hash, pre[1].account_delta_hash, 32UL ) ) has_any_mismatch = 1;
729 0 : if( 0!=memcmp( pre[0].accounts_lt_hash_checksum, pre[1].accounts_lt_hash_checksum, 32UL ) ) has_any_mismatch = 1;
730 0 : if( 0!=memcmp( pre[0].poh_hash, pre[1].poh_hash, 32UL ) ) has_any_mismatch = 1;
731 0 : if( pre[0].signature_cnt != pre[1].signature_cnt ) has_any_mismatch = 1;
732 0 : if( pre[0].account_cnt != pre[1].account_cnt ) has_any_mismatch = 1;
733 :
734 :
735 0 : if( diff->verbose < 2 ) {
736 0 : if( has_bank_hash_mismatch ) {
737 0 : printf( "\nbank hash mismatch at slot=%lu\n", pre[0].slot );
738 0 : printf( "(%s) bank_hash: %s\n"
739 0 : "(%s) bank_hash: %s\n",
740 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].bank_hash ),
741 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].bank_hash ) );
742 0 : }
743 0 : return has_bank_hash_mismatch;
744 0 : }
745 :
746 0 : if( has_any_mismatch )
747 0 : printf( "\nbank hash mismatch at slot=%lu\n", pre[0].slot );
748 :
749 0 : if( 0!=memcmp( pre[0].bank_hash, pre[1].bank_hash, 32UL ) ) {
750 0 : printf( "(%s) bank_hash: %s\n"
751 0 : "(%s) bank_hash: %s\n",
752 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].bank_hash ),
753 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].bank_hash ) );
754 0 : }
755 :
756 0 : if( 0!=memcmp( pre[0].account_delta_hash, pre[1].account_delta_hash, 32UL ) ) {
757 0 : printf( "(%s) account_delta_hash: %s\n"
758 0 : "(%s) account_delta_hash: %s\n",
759 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].account_delta_hash ),
760 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].account_delta_hash ) );
761 0 : }
762 0 : if( 0!=memcmp( pre[0].accounts_lt_hash_checksum, pre[1].accounts_lt_hash_checksum, 32UL ) ) {
763 0 : printf( "(%s) accounts_lt_hash_checksum: %s\n"
764 0 : "(%s) accounts_lt_hash_checksum: %s\n",
765 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].accounts_lt_hash_checksum ),
766 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].accounts_lt_hash_checksum ) );
767 0 : }
768 0 : if( 0!=memcmp( pre[0].prev_bank_hash, pre[1].prev_bank_hash, 32UL ) ) {
769 0 : printf( "(%s) prev_bank_hash: %s\n"
770 0 : "(%s) prev_bank_hash: %s\n",
771 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].prev_bank_hash ),
772 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].prev_bank_hash ) );
773 0 : }
774 0 : if( 0!=memcmp( pre[0].poh_hash, pre[1].poh_hash, 32UL ) ) {
775 0 : printf( "(%s) poh_hash: %s\n"
776 0 : "(%s) poh_hash: %s\n",
777 0 : diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].poh_hash ),
778 0 : diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].poh_hash ) );
779 0 : }
780 0 : if( pre[0].signature_cnt != pre[1].signature_cnt ) {
781 0 : printf( "(%s) signature_cnt: %lu\n"
782 0 : "(%s) signature_cnt: %lu\n",
783 0 : diff->file_paths[0], pre[0].signature_cnt,
784 0 : diff->file_paths[1], pre[1].signature_cnt );
785 0 : }
786 0 : if( pre[0].account_cnt != pre[1].account_cnt ) {
787 0 : printf( "(%s) account_cnt: %lu\n"
788 0 : "(%s) account_cnt: %lu\n",
789 0 : diff->file_paths[0], pre[0].account_cnt,
790 0 : diff->file_paths[1], pre[1].account_cnt );
791 0 : }
792 0 : printf( "\n" );
793 :
794 0 : int has_account_tbl_mismatch = 0;
795 0 : if( diff->verbose >= 3 ) {
796 0 : fd_scratch_push();
797 0 : has_account_tbl_mismatch = fd_solcap_diff_account_tbl( diff );
798 0 : fd_scratch_pop();
799 0 : }
800 :
801 0 : return has_any_mismatch || has_account_tbl_mismatch;
802 0 : }
803 :
804 : /* Diffs two transaction results with each other. */
805 : static void
806 0 : fd_solcap_transaction_fd_diff( fd_solcap_txn_differ_t * txn_differ ) {
807 0 : if ( FD_UNLIKELY( memcmp( txn_differ->transaction[0].txn_sig,
808 0 : txn_differ->transaction[1].txn_sig, 32UL ) != 0 ) ) {
809 : /* Transactions don't line up. */
810 0 : FD_LOG_WARNING(( "Transaction signatures are different for slot=%lu, signature=(%s != %s)."
811 0 : "It is possible that either the transactions are out of order or some transactions are missing.",
812 0 : txn_differ->transaction[0].slot,
813 0 : FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[0].txn_sig ),
814 0 : FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[1].txn_sig ) ));
815 0 : }
816 0 : else {
817 0 : bool diff_txns = txn_differ->transaction[0].fd_txn_err != txn_differ->transaction[1].fd_txn_err;
818 0 : bool diff_cus = txn_differ->transaction[0].fd_cus_used != txn_differ->transaction[1].fd_cus_used;
819 0 : bool diff_instr_err_idx = txn_differ->transaction[0].instr_err_idx != txn_differ->transaction[1].instr_err_idx;
820 0 : if ( diff_txns || diff_cus ) {
821 0 : printf(
822 0 : "\nslot: %lu\n"
823 0 : "txn_sig: '%s'\n",
824 0 : txn_differ->transaction[0].slot,
825 0 : FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[0].txn_sig) );
826 0 : }
827 0 : if ( diff_txns ) {
828 0 : printf(
829 0 : " (+) txn_err: %d\n"
830 0 : " (-) txn_err: %d\n",
831 0 : txn_differ->transaction[0].fd_txn_err,
832 0 : txn_differ->transaction[1].fd_txn_err );
833 0 : }
834 0 : if ( diff_cus ) {
835 0 : printf(
836 0 : " (+) cus_used: %lu\n"
837 0 : " (-) cus_used: %lu\n",
838 0 : txn_differ->transaction[0].fd_cus_used,
839 0 : txn_differ->transaction[1].fd_cus_used );
840 0 : }
841 0 : if ( diff_instr_err_idx ) {
842 0 : printf(
843 0 : " (+) instr_err_idx: %d\n"
844 0 : " (-) instr_err_idx: %d\n",
845 0 : txn_differ->transaction[0].instr_err_idx,
846 0 : txn_differ->transaction[1].instr_err_idx );
847 0 : }
848 0 : }
849 0 : }
850 :
851 : /* Diffs firedancer transaction result with solana's result iff it is included
852 : in the solcap. The solana result comes from rocksdb. This diff is generated
853 : from just one fd_solcap_Transaction object */
854 : static void
855 : fd_solcap_transaction_solana_diff( fd_solcap_Transaction * transaction,
856 : ulong start_slot,
857 0 : ulong end_slot ) {
858 0 : if ( transaction->slot < start_slot || transaction->slot > end_slot ) {
859 0 : return;
860 0 : }
861 :
862 0 : if ( transaction->solana_txn_err == ULONG_MAX && transaction->solana_cus_used == ULONG_MAX ) {
863 : /* If solana_txn_err and solana_cus_used are both not populated, don't print diff */
864 0 : return;
865 0 : } else if ( transaction->solana_txn_err == ULONG_MAX ) {
866 : /* Print diff if the solana_txn_err is not set (txn executed successfully) */
867 0 : transaction->solana_txn_err = 0;
868 0 : }
869 :
870 : /* Only print a diff if cus or transaction result is different */
871 0 : if( !!( transaction->fd_txn_err ) != !!( transaction->solana_txn_err ) ||
872 0 : transaction->fd_cus_used != transaction->solana_cus_used ) {
873 0 : printf(
874 0 : "slot: %lu\n"
875 0 : "txn_sig: '%s'\n"
876 0 : " (+) txn_err: %d\n"
877 0 : " (-) solana_txn_err: %lu\n"
878 0 : " (+) cus_used: %lu\n"
879 0 : " (-) solana_cus_used: %lu\n"
880 0 : " instr_err_idx: %d\n"
881 0 : " explorer: 'https://explorer.solana.com/tx/%s'\n"
882 0 : " solscan: 'https://solscan.io/tx/%s'\n"
883 0 : " solanafm: 'https://solana.fm/tx/%s'\n",
884 0 : transaction->slot,
885 0 : FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ),
886 0 : transaction->fd_txn_err,
887 0 : transaction->solana_txn_err,
888 0 : transaction->fd_cus_used,
889 0 : transaction->solana_cus_used,
890 0 : transaction->instr_err_idx,
891 0 : FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ),
892 0 : FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ),
893 0 : FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ) );
894 0 : }
895 0 : }
896 :
897 : static void
898 0 : fd_solcap_get_transaction_from_iter( fd_solcap_txn_differ_t * differ, ulong idx ) {
899 0 : if ( fd_solcap_chunk_iter_done( &differ->iter[idx] ) )
900 0 : return;
901 :
902 0 : fd_solcap_chunk_t const * chunk = fd_solcap_chunk_iter_item( &differ->iter[idx] );
903 :
904 0 : if( FD_UNLIKELY( 0!=fseek( differ->file[idx], differ->chunk_gaddr[idx] + (long)chunk->meta_coff, SEEK_SET ) ) ) {
905 0 : FD_LOG_ERR(( "fseek transaction meta failed (%d-%s)", errno, strerror( errno ) ));
906 0 : }
907 0 : if( FD_UNLIKELY( chunk->meta_sz != fread( &differ->meta_buf[idx], 1UL, chunk->meta_sz, differ->file[idx] ) ) ) {
908 0 : FD_LOG_ERR(( "fread transaction meta failed (%d-%s)", errno, strerror( errno ) ));
909 0 : }
910 :
911 0 : pb_istream_t stream = pb_istream_from_buffer( differ->meta_buf[idx], chunk->meta_sz );
912 0 : if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_Transaction_fields, &differ->transaction[idx] ) ) ) {
913 0 : FD_LOG_HEXDUMP_DEBUG(( "transaction meta", differ->meta_buf[idx], chunk->meta_sz ));
914 0 : FD_LOG_ERR(( "pb_decode transaction meta failed (%s)", PB_GET_ERROR(&stream) ));
915 0 : }
916 0 : }
917 :
918 : static void
919 0 : fd_solcap_transaction_iter( fd_solcap_txn_differ_t * txn_differ, ulong idx ) {
920 0 : while ( !fd_solcap_chunk_iter_done( &txn_differ->iter[idx] ) ) {
921 0 : txn_differ->chunk_gaddr[idx] = fd_solcap_chunk_iter_find( &txn_differ->iter[idx], FD_SOLCAP_V1_TRXN_MAGIC );
922 0 : fd_solcap_get_transaction_from_iter( txn_differ, idx );
923 0 : fd_solcap_transaction_solana_diff( &txn_differ->transaction[idx], 0, ULONG_MAX );
924 0 : }
925 0 : }
926 :
927 : static void
928 0 : fd_solcap_txn_differ_advance( fd_solcap_txn_differ_t * txn_differ ) {
929 0 : while ( !fd_solcap_chunk_iter_done( &txn_differ->iter[0] ) &&
930 0 : !fd_solcap_chunk_iter_done( &txn_differ->iter[1] ) ) {
931 : /* Diff transactions against both solana result (rocksdb) and against each other */
932 0 : fd_solcap_transaction_fd_diff( txn_differ );
933 0 : fd_solcap_transaction_solana_diff( &txn_differ->transaction[0], 0, ULONG_MAX );
934 0 : txn_differ->chunk_gaddr[0] = fd_solcap_chunk_iter_find( &txn_differ->iter[0], FD_SOLCAP_V1_TRXN_MAGIC );
935 0 : txn_differ->chunk_gaddr[1] = fd_solcap_chunk_iter_find( &txn_differ->iter[1], FD_SOLCAP_V1_TRXN_MAGIC );
936 0 : fd_solcap_get_transaction_from_iter( txn_differ, 0 );
937 0 : fd_solcap_get_transaction_from_iter( txn_differ, 1 );
938 0 : }
939 0 : }
940 :
941 0 : static void fd_solcap_txn_differ_sync( fd_solcap_txn_differ_t * txn_differ ) {
942 : /* Find first transaction for both files */
943 0 : for( int i=0; i<2; i++ ) {
944 0 : txn_differ->chunk_gaddr[i] = fd_solcap_chunk_iter_find( &txn_differ->iter[i], FD_SOLCAP_V1_TRXN_MAGIC );
945 0 : if( FD_UNLIKELY( txn_differ->chunk_gaddr[i] < 0L ) ) {
946 0 : int err = fd_solcap_chunk_iter_err( &txn_differ->iter[i] );
947 0 : if( err == 0 ) break;
948 0 : FD_LOG_ERR(( "fd_solcap_chunk_iter_next() failed (%d-%s)", err, strerror( err ) ));
949 0 : }
950 0 : }
951 :
952 : /* Get first transaction on both */
953 0 : fd_solcap_get_transaction_from_iter( txn_differ, 0 );
954 0 : fd_solcap_get_transaction_from_iter( txn_differ, 1 );
955 :
956 0 : for(;;) {
957 : /* If one is done but not the other, iterate through the rest of the
958 : transactions in order to generate a diff against solana's transactions */
959 0 : if( fd_solcap_chunk_iter_done( &txn_differ->iter[0] ) ) {
960 0 : fd_solcap_transaction_iter( txn_differ, 1 );
961 0 : break;
962 0 : } else if( fd_solcap_chunk_iter_done( &txn_differ->iter[1] ) ) {
963 0 : fd_solcap_transaction_iter( txn_differ, 0 );
964 0 : break;
965 0 : }
966 :
967 : /* Otherwise, try to sync up the two files, printing any solana diffs along the way */
968 0 : if( txn_differ->transaction[0].slot == txn_differ->transaction[1].slot ) {
969 0 : fd_solcap_txn_differ_advance( txn_differ );
970 0 : } else if( txn_differ->transaction[0].slot < txn_differ->transaction[1].slot ) {
971 : /* Advance index 0 only */
972 0 : fd_solcap_transaction_solana_diff( &txn_differ->transaction[0], 0, ULONG_MAX );
973 0 : txn_differ->chunk_gaddr[0] = fd_solcap_chunk_iter_find( &txn_differ->iter[0], FD_SOLCAP_V1_TRXN_MAGIC );
974 0 : fd_solcap_get_transaction_from_iter( txn_differ, 0 );
975 0 : } else if( txn_differ->transaction[1].slot < txn_differ->transaction[0].slot ) {
976 : /* Advance index 1 only */
977 0 : fd_solcap_transaction_solana_diff( &txn_differ->transaction[1], 0, ULONG_MAX );
978 0 : txn_differ->chunk_gaddr[1] = fd_solcap_chunk_iter_find( &txn_differ->iter[1], FD_SOLCAP_V1_TRXN_MAGIC );
979 0 : fd_solcap_get_transaction_from_iter( txn_differ, 1 );
980 0 : }
981 0 : }
982 0 : }
983 :
984 0 : static void fd_solcap_transaction_diff( FILE * file_zero, FILE * file_one ) {
985 :
986 0 : if ( FD_UNLIKELY( fseek( file_zero, 0, SEEK_SET ) != 0 ) ) {
987 0 : FD_LOG_ERR(( "fseek to start of file failed (%d-%s)", errno, strerror( errno ) ));
988 0 : }
989 0 : if ( FD_UNLIKELY( fseek( file_one, 0, SEEK_SET ) != 0 ) ) {
990 0 : FD_LOG_ERR(( "fseek to start of file failed (%d-%s)", errno, strerror( errno ) ));
991 0 : }
992 :
993 : /* Read file header and seek to first chunk to begin interation */
994 0 : fd_solcap_fhdr_t fhdr_zero[1];
995 0 : fd_solcap_fhdr_t fhdr_one[1];
996 0 : ulong n_zero = fread( fhdr_zero, sizeof(fd_solcap_fhdr_t), 1UL, file_zero );
997 0 : ulong n_one = fread( fhdr_one, sizeof(fd_solcap_fhdr_t), 1UL, file_one );
998 :
999 0 : if ( FD_UNLIKELY( n_zero != 1UL ) ) {
1000 0 : FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) ));
1001 0 : }
1002 0 : if ( FD_UNLIKELY( n_one != 1UL ) ) {
1003 0 : FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) ));
1004 0 : }
1005 0 : int err;
1006 0 : err = fseek( file_zero, (long)fhdr_zero->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR );
1007 0 : if( FD_UNLIKELY( err < 0L ) ) {
1008 0 : FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) ));
1009 0 : }
1010 0 : err = fseek( file_one, (long)fhdr_one->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR );
1011 0 : if( FD_UNLIKELY( err < 0L ) ) {
1012 0 : FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) ));
1013 0 : }
1014 :
1015 : /* Setup txn_differ */
1016 0 : fd_solcap_txn_differ_t txn_differ;
1017 0 : txn_differ.file[0] = file_zero;
1018 0 : txn_differ.file[1] = file_one;
1019 0 : fd_solcap_chunk_iter_new( &txn_differ.iter[0], file_zero );
1020 0 : fd_solcap_chunk_iter_new( &txn_differ.iter[1], file_one );
1021 :
1022 : /* Iterate and diff throught the transactions */
1023 0 : fd_solcap_txn_differ_sync( &txn_differ );
1024 0 : }
1025 :
1026 : void
1027 0 : fd_solcap_one_file_transaction_diff( FILE * file, ulong start_slot, ulong end_slot ) {
1028 : /* Open and read the header */
1029 0 : fd_solcap_fhdr_t fhdr[1];
1030 0 : ulong n = fread( fhdr, sizeof(fd_solcap_fhdr_t), 1UL, file );
1031 0 : if( FD_UNLIKELY( n!=1UL ) ) {
1032 0 : FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) ));
1033 0 : }
1034 :
1035 : /* Seek to the first chunk */
1036 0 : int err = fseek( file, (long)fhdr->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR );
1037 0 : if( FD_UNLIKELY( err<0L ) ) {
1038 0 : FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) ));
1039 0 : }
1040 :
1041 : /* Iterate through the chunks diffing the trnasactions */
1042 0 : fd_solcap_chunk_iter_t iter[1];
1043 0 : fd_solcap_chunk_iter_new( iter, file );
1044 0 : while( !fd_solcap_chunk_iter_done( iter ) ) {
1045 0 : long chunk_gaddr = fd_solcap_chunk_iter_find( iter, FD_SOLCAP_V1_TRXN_MAGIC );
1046 0 : if( FD_UNLIKELY( chunk_gaddr<0L ) ) {
1047 0 : int err = fd_solcap_chunk_iter_err( iter );
1048 0 : if( err==0 ) break;
1049 0 : FD_LOG_ERR(( "fd_solcap_chunk_iter_next() failed (%d-%s)", err, strerror( err ) ));
1050 0 : }
1051 :
1052 0 : fd_solcap_chunk_t const * chunk = fd_solcap_chunk_iter_item( iter );
1053 0 : if( FD_UNLIKELY( !chunk ) ) FD_LOG_ERR(( "fd_solcap_chunk_item() failed" ));
1054 :
1055 : /* Read transaction meta */
1056 :
1057 0 : uchar meta_buf[ 128UL ];
1058 0 : if( FD_UNLIKELY( 0!=fseek( file, chunk_gaddr + (long)chunk->meta_coff, SEEK_SET ) ) ) {
1059 0 : FD_LOG_ERR(( "fseek transaction meta failed (%d-%s)", errno, strerror( errno ) ));
1060 0 : }
1061 0 : if( FD_UNLIKELY( chunk->meta_sz != fread( meta_buf, 1UL, chunk->meta_sz, file ) ) ) {
1062 0 : FD_LOG_ERR(( "fread transaction meta failed (%d-%s)", errno, strerror( errno ) ));
1063 0 : }
1064 :
1065 : /* Deserialize transaction meta */
1066 :
1067 0 : pb_istream_t stream = pb_istream_from_buffer( meta_buf, chunk->meta_sz );
1068 :
1069 0 : fd_solcap_Transaction meta;
1070 0 : if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_Transaction_fields, &meta ) ) ) {
1071 0 : FD_LOG_HEXDUMP_DEBUG(( "transaction meta", meta_buf, chunk->meta_sz ));
1072 0 : FD_LOG_ERR(( "pb_decode transaction meta failed (%s)", PB_GET_ERROR(&stream) ));
1073 0 : }
1074 :
1075 0 : if( meta.slot < start_slot || meta.slot > end_slot ) {
1076 0 : continue;
1077 0 : }
1078 :
1079 0 : fd_solcap_transaction_solana_diff( &meta, start_slot, end_slot );
1080 0 : }
1081 0 : }
1082 :
1083 : static void
1084 0 : usage( void ) {
1085 : fprintf( stderr,
1086 0 : "Usage: fd_solcap_diff [options] {FILE1} {FILE2}\n"
1087 0 : "\n"
1088 0 : "Imports a runtime capture file from JSON.\n"
1089 0 : "\n"
1090 0 : "Options:\n"
1091 0 : " --page-sz {gigantic|huge|normal} Page size\n"
1092 0 : " --page-cnt {count} Page count\n"
1093 0 : " --scratch-mb 1024 Scratch mem MiB\n"
1094 0 : " -v 1 Diff verbosity\n"
1095 0 : " --dump-dir {dir} Dump directory\n"
1096 0 : " --start-slot {slot} Start slot\n"
1097 0 : " --end-slot {slot} End slot\n"
1098 0 : "\n" );
1099 0 : }
1100 :
1101 : int
1102 : main( int argc,
1103 : char ** argv ) {
1104 : fd_boot( &argc, &argv );
1105 :
1106 : /* Command line handling */
1107 :
1108 : for( int i=1; i<argc; i++ ) {
1109 : if( 0==strcmp( argv[i], "--help" ) ) {
1110 : usage();
1111 : return 0;
1112 : }
1113 : }
1114 :
1115 : char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" );
1116 : ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 2UL );
1117 : ulong scratch_mb = fd_env_strip_cmdline_ulong( &argc, &argv, "--scratch-mb", NULL, 1024UL );
1118 : int verbose = fd_env_strip_cmdline_int ( &argc, &argv, "-v", NULL, 1 );
1119 : char const * dump_dir = fd_env_strip_cmdline_cstr ( &argc, &argv, "--dump-dir", NULL, "dump" );
1120 : ulong start_slot = fd_env_strip_cmdline_ulong( &argc, &argv, "--start-slot", NULL, 0UL );
1121 : ulong end_slot = fd_env_strip_cmdline_ulong( &argc, &argv, "--end-slot", NULL, ULONG_MAX );
1122 :
1123 : ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz );
1124 : if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" ));
1125 :
1126 : char const * cap_path[2] = {0};
1127 : int caps_found = 0;
1128 :
1129 : for( int i=1; i<argc; i++ ) {
1130 : if( 0==strncmp( argv[i], "--", 2 ) ) continue;
1131 : if( caps_found>=2 ) { usage(); return 1; }
1132 : cap_path[ caps_found++ ] = argv[i];
1133 : }
1134 :
1135 : if( caps_found==1 ) { /* Support one file being passed in to see transaction diff */
1136 : cap_path[ caps_found++ ] = cap_path[ 0 ];
1137 : }
1138 : else if( caps_found!=2 ) {
1139 : fprintf( stderr, "ERROR: expected 2 arguments, got %d\n", argc-1 );
1140 : usage();
1141 : return 1;
1142 : }
1143 :
1144 : /* Acquire workspace */
1145 :
1146 : fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_log_cpu_id(), "wksp", 0UL );
1147 : if( FD_UNLIKELY( !wksp ) ) FD_LOG_ERR(( "fd_wksp_new_anonymous() failed" ));
1148 :
1149 : /* Create scratch allocator */
1150 :
1151 : ulong smax = scratch_mb << 20;
1152 : void * smem = fd_wksp_alloc_laddr( wksp, fd_scratch_smem_align(), smax, 1UL );
1153 : if( FD_UNLIKELY( !smem ) ) FD_LOG_ERR(( "Failed to alloc scratch mem" ));
1154 :
1155 : # define SCRATCH_DEPTH (4UL)
1156 : ulong fmem[ SCRATCH_DEPTH ] __attribute((aligned(FD_SCRATCH_FMEM_ALIGN)));
1157 :
1158 : fd_scratch_attach( smem, fmem, smax, SCRATCH_DEPTH );
1159 :
1160 : /* Open capture files for reading */
1161 :
1162 : FILE * cap_file[2] = {0};
1163 : cap_file[0] = fopen( cap_path[0], "rb" );
1164 : cap_file[1] = strcmp( cap_path[0], cap_path[1] ) ? fopen( cap_path[1], "rb" ) : cap_file[0];
1165 :
1166 : if ( FD_UNLIKELY( !cap_file[0] ) )
1167 : FD_LOG_ERR(( "fopen failed (%d-%s) on file=%s", errno, strerror( errno ), cap_path[0] ));
1168 : if ( FD_UNLIKELY( !cap_file[1] ) )
1169 : FD_LOG_ERR(( "fopen failed (%d-%s) on file=%s", errno, strerror( errno ), cap_path[1] ));
1170 :
1171 : /* Create dump dir */
1172 :
1173 : if( mkdir( dump_dir, 0777 )<0 && errno!=EEXIST )
1174 : FD_LOG_ERR(( "mkdir failed (%d-%s)", errno, strerror( errno ) ));
1175 : int dump_dir_fd = open( dump_dir, O_DIRECTORY );
1176 : if( FD_UNLIKELY( dump_dir_fd<0 ) )
1177 : FD_LOG_ERR(( "open(%s) failed (%d-%s)", dump_dir, errno, strerror( errno ) ));
1178 :
1179 : /* Handle the one file case before diffing for accounts/hashes */
1180 : if( cap_file[0] == cap_file[1] ) {
1181 : FD_LOG_NOTICE(( "Only one file was passed in. Will only print transaction diffs." ));
1182 : fd_solcap_one_file_transaction_diff( cap_file[0], start_slot, end_slot );
1183 :
1184 : /* Cleanup*/
1185 : close( dump_dir_fd );
1186 : fclose( cap_file[0] );
1187 : FD_TEST( fd_scratch_frame_used()==0UL );
1188 : fd_wksp_free_laddr( fd_scratch_detach( NULL ) );
1189 : fd_halt();
1190 : return 0;
1191 : }
1192 :
1193 : /* Create differ */
1194 :
1195 : fd_solcap_differ_t diff[1];
1196 :
1197 : /* Copy over up to last 16 chars */
1198 : char file_name_zero[SOLCAP_FILE_NAME_LEN + 1];
1199 : char file_name_one[SOLCAP_FILE_NAME_LEN + 1];
1200 : char * normalized_file_paths[2] = {file_name_zero, file_name_one};
1201 : normalize_filename( cap_path[0], normalized_file_paths[0], '+' );
1202 : normalize_filename( cap_path[1], normalized_file_paths[1], '-' );
1203 :
1204 : printf( "++%s\n", normalized_file_paths[0] );
1205 : printf( "--%s\n\n", normalized_file_paths[1] );
1206 :
1207 : if( FD_UNLIKELY( !fd_solcap_differ_new( diff, cap_file, (const char **)normalized_file_paths ) ) )
1208 : return 1;
1209 : diff->verbose = verbose;
1210 : diff->dump_dir = dump_dir;
1211 : diff->dump_dir_fd = dump_dir_fd;
1212 : int res = fd_solcap_differ_sync( diff, start_slot, end_slot );
1213 : if( res <0 ) FD_LOG_ERR(( "fd_solcap_differ_sync failed (%d-%s)",
1214 : -res, strerror( -res ) ));
1215 : if( res==0 ) FD_LOG_ERR(( "Captures don't share any slots" ));
1216 :
1217 : /* Diff each block for accounts and hashes */
1218 : int found_mismatch = 0;
1219 : for(;;) {
1220 : if( FD_UNLIKELY( fd_solcap_diff_bank( diff ) ) ) {
1221 : found_mismatch = 1;
1222 : break;
1223 : }
1224 : printf( "Slot %10lu: OK\n", diff->preimage[0].slot );
1225 : /* Advance to next slot. */
1226 : int res = fd_solcap_differ_sync( diff, start_slot, end_slot );
1227 : if( FD_UNLIKELY( res<0 ) )
1228 : FD_LOG_ERR(( "fd_solcap_differ_sync failed (%d-%s)",
1229 : -res, strerror( -res ) ));
1230 : if( res==0 ) break;
1231 : }
1232 :
1233 : /* Check both files for transaction and produce a diff if possible. If both
1234 : files contain transaction info, this will produce a diff between the two
1235 : files. If one of the files contains the solana transaction info, then we
1236 : will also print the diffs between the solana and firedancer execution. */
1237 : if ( verbose >= 4 ) {
1238 : printf( "\nTransaction diffs:\n" );
1239 : fd_solcap_transaction_diff( cap_file[0], cap_file[1] );
1240 : }
1241 :
1242 : /* Cleanup */
1243 :
1244 : close( dump_dir_fd );
1245 : fclose( cap_file[1] );
1246 : fclose( cap_file[0] );
1247 : FD_TEST( fd_scratch_frame_used()==0UL );
1248 : fd_wksp_free_laddr( fd_scratch_detach( NULL ) );
1249 : fd_halt();
1250 : return found_mismatch;
1251 : }
|