Line data Source code
1 : #define FD_SCRATCH_USE_HANDHOLDING 1
2 : #include "../fd_flamenco.h"
3 : #include "../../ballet/base58/fd_base58.h"
4 : #include "../types/fd_types.h"
5 : #include "../leaders/fd_leaders.h"
6 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
7 : #include "fd_stakes.h"
8 :
9 : #include <errno.h>
10 : #include <stdio.h>
11 : #include <stdlib.h>
12 : #include <sys/stat.h>
13 :
14 : static int
15 0 : usage( void ) {
16 0 : fprintf( stderr,
17 0 : "usage: fd_stakes_from_snapshot {nodes/leaders} {FILE}\n"
18 0 : "\n"
19 0 : "Derive epoch stake information from snapshot.\n"
20 0 : "\n"
21 0 : "Mode:\n"
22 0 : " epochs Print available epochs\n"
23 0 : " nodes Dump active stake per node identity\n"
24 0 : " CSV format: {pubkey},{stake}\n"
25 0 : " leaders Dump leader schedule\n"
26 0 : " CSV format: {slot},{pubkey}\n"
27 0 : "\n"
28 0 : "FILE is the file path to a .tar.zst snapshot or a raw\n"
29 0 : " bincode snapshot manifest\n"
30 0 : "\n"
31 0 : "Options:\n"
32 0 : "\n"
33 0 : " --page-sz {gigantic|huge|normal} Page size\n"
34 0 : " --page-cnt 2 Page count\n"
35 0 : " --scratch-mb 1024 Scratch mem MiB\n"
36 0 : " --epoch <ulong> Epoch number\n" );
37 0 : return 0;
38 0 : }
39 :
40 : #define ACTION_NODES (0)
41 : #define ACTION_LEADERS (1)
42 : #define ACTION_EPOCHS (2)
43 :
44 : /* _find_epoch looks for epoch stakes for the requested epoch.
45 : On failure, logs error and aborts application. */
46 :
47 : static fd_epoch_stakes_t const *
48 : _find_epoch( fd_solana_manifest_t const * manifest,
49 0 : ulong epoch ) {
50 :
51 :
52 0 : if( FD_UNLIKELY( epoch==ULONG_MAX ) ) {
53 0 : fprintf( stderr, "error: missing --epoch\n" );
54 0 : usage();
55 0 : exit(1);
56 0 : }
57 :
58 0 : fd_epoch_stakes_t const * stakes = NULL;
59 0 : fd_epoch_epoch_stakes_pair_t const * epochs = manifest->bank.epoch_stakes;
60 0 : for( ulong i=0; i < manifest->bank.epoch_stakes_len; i++ ) {
61 0 : if( epochs[ i ].key==epoch ) {
62 0 : stakes = &epochs[i].value;
63 0 : break;
64 0 : }
65 0 : }
66 0 : if( FD_UNLIKELY( !stakes ) )
67 0 : FD_LOG_ERR(( "Snapshot missing EpochStakes for epoch %lu", epoch ));
68 :
69 0 : return stakes;
70 0 : }
71 :
72 : static fd_stake_weight_t *
73 : _get_stake_weights( fd_solana_manifest_t const * manifest,
74 : ulong epoch,
75 0 : ulong * out_cnt ) {
76 :
77 0 : fd_epoch_stakes_t const * stakes = _find_epoch( manifest, epoch );
78 0 : fd_vote_accounts_t const * vaccs = &stakes->stakes.vote_accounts;
79 :
80 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( vaccs->vote_accounts_pool, vaccs->vote_accounts_root );
81 0 : FD_LOG_NOTICE(( "vote_acc_cnt=%lu", vote_acc_cnt ));
82 0 : fd_stake_weight_t * weights = fd_scratch_alloc( alignof(fd_stake_weight_t), vote_acc_cnt * sizeof(fd_stake_weight_t) );
83 0 : if( FD_UNLIKELY( !weights ) ) FD_LOG_ERR(( "fd_scratch_alloc() failed" ));
84 :
85 0 : ulong weight_cnt = fd_stake_weights_by_node( vaccs, weights );
86 0 : if( FD_UNLIKELY( weight_cnt==ULONG_MAX ) ) FD_LOG_ERR(( "fd_stake_weights_by_node() failed" ));
87 :
88 0 : *out_cnt = weight_cnt;
89 0 : return weights;
90 0 : }
91 :
92 : static int
93 0 : action_epochs( fd_solana_manifest_t const * manifest ) {
94 0 : fd_epoch_epoch_stakes_pair_t const * epochs = manifest->bank.epoch_stakes;
95 0 : for( ulong i=0; i < manifest->bank.epoch_stakes_len; i++ )
96 0 : printf( "%lu\n", epochs[ i ].key );
97 0 : return 0;
98 0 : }
99 :
100 : static int
101 : action_nodes( fd_solana_manifest_t const * manifest,
102 0 : ulong epoch ) {
103 :
104 0 : ulong weight_cnt;
105 0 : fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt );
106 :
107 0 : for( ulong i=0UL; i<weight_cnt; i++ ) {
108 0 : fd_stake_weight_t const * w = weights + i;
109 0 : char keyB58[ FD_BASE58_ENCODED_32_SZ ];
110 0 : fd_base58_encode_32( w->key.key, NULL, keyB58 );
111 0 : printf( "%s,%lu\n", keyB58, w->stake );
112 0 : }
113 :
114 0 : return 0;
115 0 : }
116 :
117 : static int
118 : action_leaders( fd_solana_manifest_t const * manifest,
119 0 : ulong epoch ) {
120 :
121 0 : ulong weight_cnt;
122 0 : fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt );
123 :
124 0 : fd_epoch_schedule_t const * sched = &manifest->bank.epoch_schedule;
125 0 : ulong slot0 = fd_epoch_slot0 ( sched, epoch );
126 0 : ulong slot_cnt = fd_epoch_slot_cnt( sched, epoch );
127 0 : ulong sched_cnt = slot_cnt/FD_EPOCH_SLOTS_PER_ROTATION;
128 :
129 0 : void * leaders_mem = fd_scratch_alloc( fd_epoch_leaders_align(), fd_epoch_leaders_footprint( weight_cnt, sched_cnt ) );
130 0 : leaders_mem = fd_epoch_leaders_new( leaders_mem, epoch, slot0, slot_cnt, weight_cnt, weights, 0UL );
131 0 : fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( leaders_mem );
132 0 : FD_TEST( leaders );
133 :
134 0 : ulong slot = slot0;
135 0 : for( ulong i=0; i<sched_cnt; i++ ) {
136 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, slot );
137 0 : char keyB58[ FD_BASE58_ENCODED_32_SZ ];
138 0 : fd_base58_encode_32( leader->key, NULL, keyB58 );
139 0 : for( ulong j=0; j<FD_EPOCH_SLOTS_PER_ROTATION; j++ ) {
140 0 : printf( "%lu,%s\n", slot, keyB58 );
141 0 : slot++;
142 0 : }
143 0 : }
144 :
145 0 : return 0;
146 0 : }
147 :
148 : /* _is_zstd returns 1 if given file handle points to the beginning of a
149 : zstd stream, otherwise zero. */
150 :
151 : static int
152 0 : _is_zstd( FILE * file ) {
153 0 : uint magic;
154 0 : ulong n = fread( &magic, 1UL, 4UL, file );
155 0 : if( FD_UNLIKELY( feof( file ) ) ) {
156 0 : clearerr( file );
157 0 : fseek( file, -(long)n, SEEK_CUR );
158 0 : return 0;
159 0 : }
160 0 : int err = ferror( file );
161 0 : if( FD_UNLIKELY( err ) )
162 0 : FD_LOG_ERR(( "fread() failed (%d-%s)", err, strerror( err ) ));
163 0 : fseek( file, -4L, SEEK_CUR );
164 0 : return ( magic==0xFD2FB528UL );
165 0 : }
166 :
167 : /* TODO older Solana snapshots are gzip, add support */
168 :
169 : int
170 : main( int argc,
171 : char ** argv ) {
172 : fd_boot( &argc, &argv );
173 : fd_flamenco_boot( &argc, &argv );
174 :
175 : for( int i=1; i<argc; i++ )
176 : if( 0==strcmp( argv[i], "--help" ) ) return usage();
177 :
178 : char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" );
179 : ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 2UL );
180 : ulong scratch_mb = fd_env_strip_cmdline_ulong( &argc, &argv, "--scratch-mb", NULL, 1024UL );
181 : ulong epoch = fd_env_strip_cmdline_ulong( &argc, &argv, "--epoch", NULL, ULONG_MAX );
182 :
183 : ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz );
184 : if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" ));
185 :
186 : if( FD_UNLIKELY( argc!=3 ) ) {
187 : fprintf( stderr, "error: missing arguments\n" );
188 : return usage();
189 : }
190 :
191 : char const * mode = argv[1];
192 : char const * filepath = argv[2];
193 :
194 : int action;
195 : /**/ if( 0==strcmp( mode, "epochs" ) ) action = ACTION_EPOCHS;
196 : else if( 0==strcmp( mode, "nodes" ) ) action = ACTION_NODES;
197 : else if( 0==strcmp( mode, "leaders" ) ) action = ACTION_LEADERS;
198 : else {
199 : fprintf( stderr, "error: invalid mode \"%s\"\n", mode );
200 : return usage();
201 : }
202 :
203 : /* Create workspace and scratch allocator */
204 :
205 : fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_log_cpu_id(), "wksp", 0UL );
206 : if( FD_UNLIKELY( !wksp ) ) FD_LOG_ERR(( "fd_wksp_new_anonymous() failed" ));
207 :
208 : ulong smax = scratch_mb << 20;
209 : void * smem = fd_wksp_alloc_laddr( wksp, fd_scratch_smem_align(), smax, 1UL );
210 : if( FD_UNLIKELY( !smem ) ) FD_LOG_ERR(( "Failed to alloc scratch mem" ));
211 : ulong scratch_depth = 4UL;
212 : void * fmem = fd_wksp_alloc_laddr( wksp, fd_scratch_fmem_align(), fd_scratch_fmem_footprint( scratch_depth ), 2UL );
213 : if( FD_UNLIKELY( !fmem ) ) FD_LOG_ERR(( "Failed to alloc scratch frames" ));
214 :
215 : fd_scratch_attach( smem, fmem, smax, scratch_depth );
216 : fd_scratch_push();
217 : fd_valloc_t scratch_valloc = fd_scratch_virtual();
218 :
219 : /* Open file */
220 :
221 : FD_LOG_INFO(( "Reading snapshot %s", filepath ));
222 : FILE * file = fopen( filepath, "rb" );
223 : if( FD_UNLIKELY( !file ) ) FD_LOG_ERR(( "fopen(%s) failed (%d-%s)", filepath, errno, strerror( errno ) ));
224 :
225 : /* Read manifest bytes */
226 :
227 : void * manifest_bin;
228 : ulong manifest_binsz;
229 : if( _is_zstd( file ) ) {
230 : FD_LOG_NOTICE(( "Detected .tar.zst stream" ));
231 : FD_LOG_ERR(( "TODO" ));
232 : } else {
233 : FD_LOG_NOTICE(( "Assuming raw bincode file" ));
234 :
235 : /* Allocate buffer suitable for storing file */
236 :
237 : struct stat stat;
238 : int err = fstat( fileno( file ), &stat );
239 : if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fstat() failed (%d-%s)", errno, strerror( errno ) ));
240 : manifest_binsz = (ulong)stat.st_size;
241 :
242 : manifest_bin = fd_wksp_alloc_laddr( wksp, 1UL, manifest_binsz, 3UL );
243 : if( FD_UNLIKELY( !manifest_bin ) ) FD_LOG_ERR(( "failed to allocate metadata buffer" ));
244 :
245 : /* Read file into buffer */
246 :
247 : ulong n = fread( manifest_bin, manifest_binsz, 1UL, file );
248 : if( FD_UNLIKELY( n!=1UL ) ) FD_LOG_ERR(( "fread() failed (eof=%d)", feof( file ) ));
249 : FD_LOG_NOTICE(( "Read manifest (%.3f MiB)", (double)manifest_binsz/(1UL<<20) ));
250 : }
251 :
252 : /* Deserialize manifest */
253 :
254 : long dt = -fd_log_wallclock();
255 : fd_solana_manifest_t manifest;
256 :
257 : fd_bincode_decode_ctx_t decode = {
258 : .valloc = scratch_valloc,
259 : .data = manifest_bin,
260 : .dataend = (void *)( (ulong)manifest_bin + manifest_binsz ),
261 : };
262 : if( FD_UNLIKELY( FD_BINCODE_SUCCESS!=
263 : fd_solana_manifest_decode( &manifest, &decode ) ) )
264 : FD_LOG_ERR(( "Failed to deserialize manifest" ));
265 :
266 : fd_wksp_free_laddr( manifest_bin ); manifest_bin = NULL;
267 : dt += fd_log_wallclock();
268 : FD_LOG_NOTICE(( "Deserialized manifest (took %.3fs)", (double)dt/1e9 ));
269 :
270 : /* Action */
271 :
272 : int res;
273 : fd_scratch_push();
274 : switch( action ) {
275 : case ACTION_LEADERS:
276 : res = action_leaders( &manifest, epoch );
277 : break;
278 : case ACTION_NODES:
279 : res = action_nodes( &manifest, epoch );
280 : break;
281 : case ACTION_EPOCHS:
282 : res = action_epochs( &manifest );
283 : break;
284 : default:
285 : __builtin_unreachable();
286 : }
287 : fd_scratch_pop();
288 :
289 : /* Cleanup */
290 :
291 : fd_scratch_pop();
292 : FD_TEST( fd_scratch_frame_used()==0UL );
293 : fd_wksp_free_laddr( fd_scratch_detach( NULL ) );
294 : fd_wksp_free_laddr( fmem );
295 : fclose( file );
296 : fd_wksp_detach( wksp );
297 : fd_flamenco_halt();
298 : fd_halt();
299 : return res;
300 : }
|