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_vote_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_vote_stake_weight_t * weights = fd_scratch_alloc( alignof(fd_vote_stake_weight_t), vote_acc_cnt * sizeof(fd_vote_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( NULL, 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_vote_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_vote_stake_weight_t const * w = weights + i;
109 0 : char keyB58[ FD_BASE58_ENCODED_32_SZ ];
110 0 : fd_base58_encode_32( w->id_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_vote_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 : ulong vote_keyed_lsched = 0; /* Set to 1 after SIMD-0180 gets activated */
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, vote_keyed_lsched );
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 :
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 :
218 : /* Open file */
219 :
220 : FD_LOG_INFO(( "Reading snapshot %s", filepath ));
221 : FILE * file = fopen( filepath, "rb" );
222 : if( FD_UNLIKELY( !file ) ) FD_LOG_ERR(( "fopen(%s) failed (%d-%s)", filepath, errno, strerror( errno ) ));
223 :
224 : /* Read manifest bytes */
225 :
226 : void * manifest_bin;
227 : ulong manifest_binsz;
228 : if( _is_zstd( file ) ) {
229 : FD_LOG_NOTICE(( "Detected .tar.zst stream" ));
230 : FD_LOG_ERR(( "TODO" ));
231 : } else {
232 : FD_LOG_NOTICE(( "Assuming raw bincode file" ));
233 :
234 : /* Allocate buffer suitable for storing file */
235 :
236 : struct stat stat;
237 : int err = fstat( fileno( file ), &stat );
238 : if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fstat() failed (%d-%s)", errno, strerror( errno ) ));
239 : manifest_binsz = (ulong)stat.st_size;
240 :
241 : manifest_bin = fd_wksp_alloc_laddr( wksp, 1UL, manifest_binsz, 3UL );
242 : if( FD_UNLIKELY( !manifest_bin ) ) FD_LOG_ERR(( "failed to allocate metadata buffer" ));
243 :
244 : /* Read file into buffer */
245 :
246 : ulong n = fread( manifest_bin, manifest_binsz, 1UL, file );
247 : if( FD_UNLIKELY( n!=1UL ) ) FD_LOG_ERR(( "fread() failed (eof=%d)", feof( file ) ));
248 : FD_LOG_NOTICE(( "Read manifest (%.3f MiB)", (double)manifest_binsz/(1UL<<20) ));
249 : }
250 :
251 : /* Deserialize manifest */
252 :
253 : long dt = -fd_log_wallclock();
254 :
255 : int decode_err;
256 : fd_solana_manifest_t * manifest = fd_bincode_decode_scratch(
257 : solana_manifest, manifest_bin, manifest_binsz, &decode_err );
258 : if( FD_UNLIKELY( decode_err ) ) {
259 : FD_LOG_ERR(( "Failed to decode manifest" ));
260 : }
261 :
262 : fd_wksp_free_laddr( manifest_bin ); manifest_bin = NULL;
263 : dt += fd_log_wallclock();
264 : FD_LOG_NOTICE(( "Deserialized manifest (took %.3fs)", (double)dt/1e9 ));
265 :
266 : /* Action */
267 :
268 : int res;
269 : fd_scratch_push();
270 : switch( action ) {
271 : case ACTION_LEADERS:
272 : res = action_leaders( manifest, epoch );
273 : break;
274 : case ACTION_NODES:
275 : res = action_nodes( manifest, epoch );
276 : break;
277 : case ACTION_EPOCHS:
278 : res = action_epochs( manifest );
279 : break;
280 : default:
281 : __builtin_unreachable();
282 : }
283 : fd_scratch_pop();
284 :
285 : /* Cleanup */
286 :
287 : fd_scratch_pop();
288 : FD_TEST( fd_scratch_frame_used()==0UL );
289 : fd_wksp_free_laddr( fd_scratch_detach( NULL ) );
290 : fd_wksp_free_laddr( fmem );
291 : fclose( file );
292 : fd_wksp_detach( wksp );
293 : fd_halt();
294 : return res;
295 : }
|