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 : /* FIXME: This will crash because the spad is not set up. */
86 0 : ulong weight_cnt = fd_stake_weights_by_node( vaccs, weights, NULL );
87 0 : if( FD_UNLIKELY( weight_cnt==ULONG_MAX ) ) FD_LOG_ERR(( "fd_stake_weights_by_node() failed" ));
88 :
89 0 : *out_cnt = weight_cnt;
90 0 : return weights;
91 0 : }
92 :
93 : static int
94 0 : action_epochs( fd_solana_manifest_t const * manifest ) {
95 0 : fd_epoch_epoch_stakes_pair_t const * epochs = manifest->bank.epoch_stakes;
96 0 : for( ulong i=0; i < manifest->bank.epoch_stakes_len; i++ )
97 0 : printf( "%lu\n", epochs[ i ].key );
98 0 : return 0;
99 0 : }
100 :
101 : static int
102 : action_nodes( fd_solana_manifest_t const * manifest,
103 0 : ulong epoch ) {
104 :
105 0 : ulong weight_cnt;
106 0 : fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt );
107 :
108 0 : for( ulong i=0UL; i<weight_cnt; i++ ) {
109 0 : fd_stake_weight_t const * w = weights + i;
110 0 : char keyB58[ FD_BASE58_ENCODED_32_SZ ];
111 0 : fd_base58_encode_32( w->key.key, NULL, keyB58 );
112 0 : printf( "%s,%lu\n", keyB58, w->stake );
113 0 : }
114 :
115 0 : return 0;
116 0 : }
117 :
118 : static int
119 : action_leaders( fd_solana_manifest_t const * manifest,
120 0 : ulong epoch ) {
121 :
122 0 : ulong weight_cnt;
123 0 : fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt );
124 :
125 0 : fd_epoch_schedule_t const * sched = &manifest->bank.epoch_schedule;
126 0 : ulong slot0 = fd_epoch_slot0 ( sched, epoch );
127 0 : ulong slot_cnt = fd_epoch_slot_cnt( sched, epoch );
128 0 : ulong sched_cnt = slot_cnt/FD_EPOCH_SLOTS_PER_ROTATION;
129 :
130 0 : void * leaders_mem = fd_scratch_alloc( fd_epoch_leaders_align(), fd_epoch_leaders_footprint( weight_cnt, sched_cnt ) );
131 0 : leaders_mem = fd_epoch_leaders_new( leaders_mem, epoch, slot0, slot_cnt, weight_cnt, weights, 0UL );
132 0 : fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( leaders_mem );
133 0 : FD_TEST( leaders );
134 :
135 0 : ulong slot = slot0;
136 0 : for( ulong i=0; i<sched_cnt; i++ ) {
137 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, slot );
138 0 : char keyB58[ FD_BASE58_ENCODED_32_SZ ];
139 0 : fd_base58_encode_32( leader->key, NULL, keyB58 );
140 0 : for( ulong j=0; j<FD_EPOCH_SLOTS_PER_ROTATION; j++ ) {
141 0 : printf( "%lu,%s\n", slot, keyB58 );
142 0 : slot++;
143 0 : }
144 0 : }
145 :
146 0 : return 0;
147 0 : }
148 :
149 : /* _is_zstd returns 1 if given file handle points to the beginning of a
150 : zstd stream, otherwise zero. */
151 :
152 : static int
153 0 : _is_zstd( FILE * file ) {
154 0 : uint magic;
155 0 : ulong n = fread( &magic, 1UL, 4UL, file );
156 0 : if( FD_UNLIKELY( feof( file ) ) ) {
157 0 : clearerr( file );
158 0 : fseek( file, -(long)n, SEEK_CUR );
159 0 : return 0;
160 0 : }
161 0 : int err = ferror( file );
162 0 : if( FD_UNLIKELY( err ) )
163 0 : FD_LOG_ERR(( "fread() failed (%d-%s)", err, strerror( err ) ));
164 0 : fseek( file, -4L, SEEK_CUR );
165 0 : return ( magic==0xFD2FB528UL );
166 0 : }
167 :
168 : /* TODO older Solana snapshots are gzip, add support */
169 :
170 : int
171 : main( int argc,
172 : char ** argv ) {
173 : fd_boot( &argc, &argv );
174 : fd_flamenco_boot( &argc, &argv );
175 :
176 : for( int i=1; i<argc; i++ )
177 : if( 0==strcmp( argv[i], "--help" ) ) return usage();
178 :
179 : char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" );
180 : ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 2UL );
181 : ulong scratch_mb = fd_env_strip_cmdline_ulong( &argc, &argv, "--scratch-mb", NULL, 1024UL );
182 : ulong epoch = fd_env_strip_cmdline_ulong( &argc, &argv, "--epoch", NULL, ULONG_MAX );
183 :
184 : ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz );
185 : if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" ));
186 :
187 : if( FD_UNLIKELY( argc!=3 ) ) {
188 : fprintf( stderr, "error: missing arguments\n" );
189 : return usage();
190 : }
191 :
192 : char const * mode = argv[1];
193 : char const * filepath = argv[2];
194 :
195 : int action;
196 : /**/ if( 0==strcmp( mode, "epochs" ) ) action = ACTION_EPOCHS;
197 : else if( 0==strcmp( mode, "nodes" ) ) action = ACTION_NODES;
198 : else if( 0==strcmp( mode, "leaders" ) ) action = ACTION_LEADERS;
199 : else {
200 : fprintf( stderr, "error: invalid mode \"%s\"\n", mode );
201 : return usage();
202 : }
203 :
204 : /* Create workspace and scratch allocator */
205 :
206 : fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_log_cpu_id(), "wksp", 0UL );
207 : if( FD_UNLIKELY( !wksp ) ) FD_LOG_ERR(( "fd_wksp_new_anonymous() failed" ));
208 :
209 : ulong smax = scratch_mb << 20;
210 : void * smem = fd_wksp_alloc_laddr( wksp, fd_scratch_smem_align(), smax, 1UL );
211 : if( FD_UNLIKELY( !smem ) ) FD_LOG_ERR(( "Failed to alloc scratch mem" ));
212 : ulong scratch_depth = 4UL;
213 : void * fmem = fd_wksp_alloc_laddr( wksp, fd_scratch_fmem_align(), fd_scratch_fmem_footprint( scratch_depth ), 2UL );
214 : if( FD_UNLIKELY( !fmem ) ) FD_LOG_ERR(( "Failed to alloc scratch frames" ));
215 :
216 : fd_scratch_attach( smem, fmem, smax, scratch_depth );
217 : fd_scratch_push();
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 :
256 : fd_bincode_decode_ctx_t decode = {
257 : .data = manifest_bin,
258 : .dataend = (void *)( (ulong)manifest_bin + manifest_binsz ),
259 : };
260 :
261 : ulong total_sz = 0UL;
262 : int decode_err = fd_solana_manifest_decode_footprint( &decode, &total_sz );
263 : if( FD_UNLIKELY( decode_err ) ) {
264 : FD_LOG_ERR(( "Failed to decode manifest" ));
265 : }
266 :
267 : uchar * mem = fd_scratch_alloc( alignof(fd_solana_manifest_t), total_sz );
268 : if( FD_UNLIKELY( !mem ) ) {
269 : FD_LOG_ERR(( "Unable to allocate memory for manifest" ));
270 : }
271 :
272 : fd_solana_manifest_t * manifest = fd_solana_manifest_decode( mem, &decode );
273 :
274 : fd_wksp_free_laddr( manifest_bin ); manifest_bin = NULL;
275 : dt += fd_log_wallclock();
276 : FD_LOG_NOTICE(( "Deserialized manifest (took %.3fs)", (double)dt/1e9 ));
277 :
278 : /* Action */
279 :
280 : int res;
281 : fd_scratch_push();
282 : switch( action ) {
283 : case ACTION_LEADERS:
284 : res = action_leaders( manifest, epoch );
285 : break;
286 : case ACTION_NODES:
287 : res = action_nodes( manifest, epoch );
288 : break;
289 : case ACTION_EPOCHS:
290 : res = action_epochs( manifest );
291 : break;
292 : default:
293 : __builtin_unreachable();
294 : }
295 : fd_scratch_pop();
296 :
297 : /* Cleanup */
298 :
299 : fd_scratch_pop();
300 : FD_TEST( fd_scratch_frame_used()==0UL );
301 : fd_wksp_free_laddr( fd_scratch_detach( NULL ) );
302 : fd_wksp_free_laddr( fmem );
303 : fclose( file );
304 : fd_wksp_detach( wksp );
305 : fd_flamenco_halt();
306 : fd_halt();
307 : return res;
308 : }
|