LCOV - code coverage report
Current view: top level - flamenco/capture - fd_solcap_diff.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 768 0.0 %
Date: 2025-10-13 04:42:14 Functions: 0 22 0.0 %

          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             : }

Generated by: LCOV version 1.14