Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_genesi_tile.h"
3 : #include "fd_genesis_client.h"
4 : #include "../../disco/topo/fd_topo.h"
5 : #include "../../disco/topo/fd_dns_resolve.h"
6 : #include "../../disco/waker/fd_waker.h"
7 : #include "../../disco/fd_clock_tile.h"
8 : #include "../../flamenco/accdb/fd_accdb.h"
9 : #include "../../flamenco/accdb/fd_accdb_shmem.h"
10 : #include "../../flamenco/genesis/fd_genesis_parse.h"
11 : #include "../../disco/events/generated/fd_event_gen.h"
12 : #include "../../third_party/bzip2/bzlib.h"
13 : #include "../../ballet/sha256/fd_sha256.h"
14 : #include "../../flamenco/runtime/fd_hashes.h"
15 : #include "../../util/archive/fd_tar.h"
16 :
17 : #include <stdio.h>
18 : #include <errno.h>
19 : #include <fcntl.h>
20 : #include <poll.h>
21 : #include <sys/syscall.h>
22 : #include <unistd.h>
23 : #include <netinet/in.h>
24 : #include <linux/fs.h>
25 :
26 : #include <sys/socket.h>
27 : #include <linux/futex.h>
28 : #include "generated/fd_genesi_tile_seccomp.h"
29 :
30 : static void *
31 : bz2_malloc( void * opaque,
32 : int items,
33 0 : int size ) {
34 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
35 :
36 0 : if( FD_UNLIKELY( items<=0 || size<=0 ) ) {
37 0 : FD_LOG_WARNING(( "bz2_malloc: invalid items=%d or size=%d", items, size ));
38 0 : return NULL;
39 0 : }
40 0 : void * result = fd_alloc_malloc( alloc, alignof(max_align_t), (ulong)items*(ulong)size );
41 0 : if( FD_UNLIKELY( !result ) ) return NULL;
42 0 : return result;
43 0 : }
44 :
45 : static void
46 : bz2_free( void * opaque,
47 0 : void * addr ) {
48 0 : fd_alloc_t * alloc = (fd_alloc_t *)opaque;
49 :
50 0 : if( FD_UNLIKELY( !addr ) ) return;
51 0 : fd_alloc_free( alloc, addr );
52 0 : }
53 :
54 : struct fd_genesi_tile {
55 : fd_accdb_t * accdb;
56 : fd_accdb_fork_id_t accdb_root;
57 :
58 : fd_hash_t genesis_hash[1];
59 :
60 : fd_genesis_client_t * client;
61 :
62 : ulong waker_client_idx;
63 : ulong * waker_fseq;
64 :
65 : fd_clock_tile_t clock[1];
66 :
67 : fd_ip4_port_t entrypoints[ FD_TOPO_GOSSIP_ENTRYPOINTS_MAX ];
68 : ulong entrypoints_cnt;
69 :
70 : fd_lthash_value_t lthash[1];
71 :
72 : int local_genesis;
73 : int bootstrap;
74 : int shutdown;
75 :
76 : int has_expected_genesis_hash;
77 : uchar expected_genesis_hash[ 32UL ];
78 : ushort expected_shred_version;
79 : int validate_genesis_hash;
80 :
81 : char genesis_path[ PATH_MAX ];
82 :
83 : int in_fd;
84 : int out_fd;
85 : int out_dir_fd;
86 :
87 : struct {
88 : fd_wksp_t * mem;
89 : ulong chunk0;
90 : } out;
91 :
92 : fd_alloc_t * bz2_alloc;
93 :
94 : fd_genesis_t * genesis;
95 : uchar * genesis_blob;
96 : ulong genesis_blob_sz;
97 : ulong max_message_size;
98 : };
99 :
100 : typedef struct fd_genesi_tile fd_genesi_tile_t;
101 :
102 : FD_FN_CONST static inline ulong
103 0 : scratch_align( void ) {
104 0 : ulong a = alignof( fd_genesi_tile_t );
105 0 : a = fd_ulong_max( a, fd_genesis_client_align() );
106 0 : a = fd_ulong_max( a, fd_alloc_align() );
107 0 : a = fd_ulong_max( a, fd_genesis_align() );
108 0 : return a;
109 0 : }
110 :
111 : FD_FN_PURE static inline ulong
112 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
113 0 : ulong l = FD_LAYOUT_INIT;
114 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_genesi_tile_t ), sizeof( fd_genesi_tile_t ) );
115 0 : l = FD_LAYOUT_APPEND( l, fd_genesis_client_align(), fd_genesis_client_footprint() );
116 0 : l = FD_LAYOUT_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
117 0 : if( FD_UNLIKELY( !tile->genesi.entrypoints_cnt ) ) {
118 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->genesi.max_live_slots, 0 ) );
119 0 : }
120 0 : l = FD_LAYOUT_APPEND( l, fd_genesis_align(), fd_genesis_footprint( fd_genesis_account_max( tile->genesi.max_message_size ) ) );
121 0 : l = FD_LAYOUT_APPEND( l, alignof(uchar), tile->genesi.max_message_size + 4UL*FD_TAR_BLOCK_SZ );
122 0 : return FD_LAYOUT_FINI( l, scratch_align() );
123 0 : }
124 :
125 : FD_FN_CONST static inline ulong
126 0 : loose_footprint( fd_topo_tile_t const * tile ) {
127 0 : (void)tile;
128 : /* Leftover space for bzip2 allocations */
129 0 : return 1UL<<26; /* 64 MiB */
130 0 : }
131 :
132 : static inline int
133 0 : should_shutdown( fd_genesi_tile_t * ctx ) {
134 0 : return ctx->shutdown;
135 0 : }
136 :
137 : static void
138 0 : during_housekeeping( fd_genesi_tile_t * ctx ) {
139 0 : if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
140 0 : }
141 :
142 : static long
143 0 : next_deadline( fd_genesi_tile_t * ctx ) {
144 0 : if( FD_LIKELY( ctx->shutdown || ctx->local_genesis ) ) return LONG_MAX;
145 :
146 0 : long deadline = fd_genesis_client_deadline_nanos( ctx->client );
147 0 : if( FD_UNLIKELY( deadline==LONG_MAX ) ) return 0L; /* first poll starts the clock */
148 0 : return fd_clock_tile_wallclock_to_tickcount( ctx->clock, deadline );
149 0 : }
150 :
151 : static fd_accdb_fork_id_t
152 : initialize_accdb( fd_accdb_t * accdb,
153 : fd_genesis_t const * genesis,
154 : uchar const * genesis_blob,
155 0 : fd_lthash_value_t * lthash ) {
156 0 : fd_accdb_fork_id_t fork_id = fd_accdb_attach_child( accdb, (fd_accdb_fork_id_t) {.val = USHORT_MAX } );
157 :
158 0 : for( ulong i=0UL; i<genesis->account_cnt; i++ ) {
159 0 : fd_genesis_account_t account[1];
160 0 : fd_genesis_account( genesis, genesis_blob, account, i );
161 :
162 0 : fd_acc_t acc = fd_accdb_write_one( accdb, fork_id, account->pubkey.key );
163 0 : fd_memcpy( acc.owner, account->owner.uc, 32UL );
164 0 : acc.lamports = account->lamports;
165 0 : acc.executable = !!account->executable;
166 0 : acc.data_len = account->data_len;
167 0 : fd_memcpy( acc.data, account->data, account->data_len );
168 :
169 0 : fd_lthash_value_t new_hash[1];
170 0 : fd_hashes_account_lthash_simple( account->pubkey.uc, acc.owner, acc.lamports, acc.executable, account->data, account->data_len, new_hash );
171 0 : fd_lthash_add( lthash, new_hash );
172 :
173 0 : acc.commit = 1;
174 0 : fd_accdb_unwrite_one( accdb, &acc );
175 0 : }
176 0 : return fork_id;
177 0 : }
178 :
179 : static inline void
180 : verify_cluster_type( fd_genesis_t const * genesis,
181 : fd_hash_t const * genesis_hash,
182 0 : char const * genesis_path ) {
183 :
184 0 : fd_hash_t mainnet_hash[1];
185 0 : FD_TEST( fd_base58_decode_32( "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", mainnet_hash->uc ) );
186 :
187 0 : fd_hash_t testnet_hash[1];
188 0 : FD_TEST( fd_base58_decode_32( "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY", testnet_hash->uc ) );
189 :
190 0 : fd_hash_t devnet_hash[1];
191 0 : FD_TEST( fd_base58_decode_32( "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", devnet_hash->uc ) );
192 :
193 0 : switch( genesis->cluster_type ) {
194 0 : case FD_GENESIS_TYPE_MAINNET: {
195 0 : if( FD_UNLIKELY( !fd_hash_eq( genesis_hash, mainnet_hash ) ) ) {
196 0 : FD_BASE58_ENCODE_32_BYTES( genesis_hash->uc, genesis_hash_b58 );
197 0 : FD_LOG_ERR(( "genesis file `%s` has cluster type MAINNET but unexpected genesis hash `%s`",
198 0 : genesis_path, genesis_hash_b58 ));
199 0 : }
200 0 : break;
201 0 : }
202 0 : case FD_GENESIS_TYPE_TESTNET: {
203 0 : if( FD_UNLIKELY( !fd_hash_eq( genesis_hash, testnet_hash ) ) ) {
204 0 : FD_BASE58_ENCODE_32_BYTES( genesis_hash->uc, genesis_hash_b58 );
205 0 : FD_LOG_ERR(( "genesis file `%s` has cluster type TESTNET but unexpected genesis hash `%s`",
206 0 : genesis_path, genesis_hash_b58 ));
207 0 : }
208 0 : break;
209 0 : }
210 0 : case FD_GENESIS_TYPE_DEVNET: {
211 0 : if( FD_UNLIKELY( !fd_hash_eq( genesis_hash, devnet_hash ) ) ) {
212 0 : FD_BASE58_ENCODE_32_BYTES( genesis_hash->uc, genesis_hash_b58 );
213 0 : FD_LOG_ERR(( "genesis file `%s` has cluster type DEVNET but unexpected genesis hash `%s`",
214 0 : genesis_path, genesis_hash_b58 ));
215 0 : }
216 0 : break;
217 0 : }
218 0 : default:
219 0 : break;
220 0 : }
221 0 : }
222 :
223 : static void
224 : after_credit( fd_genesi_tile_t * ctx,
225 : fd_stem_context_t * stem,
226 : int * opt_poll_in,
227 0 : int * charge_busy ) {
228 0 : (void)opt_poll_in;
229 :
230 0 : if( FD_UNLIKELY( ctx->shutdown ) ) return;
231 :
232 0 : if( FD_LIKELY( ctx->local_genesis ) ) {
233 0 : FD_TEST( -1!=ctx->in_fd );
234 :
235 0 : if( FD_UNLIKELY( ctx->genesis_blob_sz>ctx->max_message_size ) ) {
236 0 : FD_LOG_ERR(( "Genesis file `%s` exceeds maximum size (blob_sz=%lu max=%lu)",
237 0 : ctx->genesis_path, ctx->genesis_blob_sz, ctx->max_message_size ));
238 0 : }
239 0 : ulong msg_sz = sizeof(fd_genesis_meta_t) + ctx->genesis_blob_sz;
240 0 : if( FD_UNLIKELY( msg_sz>fd_genesi_tile_mtu( ctx->max_message_size ) ) ) {
241 0 : FD_LOG_ERR(( "The genesis file `%s` is too large for this Firedancer configuration (msg_sz=%lu max=%lu).\n"
242 0 : "Cannot start Firedancer. Please use a different genesis config or increase `development.genesis.max_file_size_mib`.",
243 0 : ctx->genesis_path, msg_sz, fd_genesi_tile_mtu( ctx->max_message_size ) ));
244 0 : }
245 :
246 0 : fd_genesis_meta_t * dst = fd_chunk_to_laddr( ctx->out.mem, ctx->out.chunk0 );
247 0 : memset( dst, 0, sizeof(fd_genesis_meta_t) );
248 0 : dst->creation_time_seconds = ctx->genesis->creation_time;
249 0 : dst->genesis_hash = *ctx->genesis_hash;
250 :
251 0 : if( FD_UNLIKELY( ctx->bootstrap ) ) {
252 0 : dst->accdb_root = ctx->accdb_root;
253 0 : dst->bootstrap = 1;
254 0 : dst->has_lthash = 1;
255 0 : dst->lthash = *ctx->lthash;
256 0 : }
257 :
258 0 : uchar * dst_blob = (uchar *)( dst+1 );
259 0 : dst->blob_sz = ctx->genesis_blob_sz;
260 0 : fd_memcpy( dst_blob, ctx->genesis_blob, ctx->genesis_blob_sz );
261 :
262 0 : fd_stem_publish( stem, 0UL, msg_sz, ctx->out.chunk0, 0UL, 0UL, 0UL, 0UL );
263 0 : *charge_busy = 1;
264 0 : FD_LOG_NOTICE(( "loaded local genesis.bin from file %s%s%s", fd_log_style_dim(), ctx->genesis_path, fd_log_style_normal() ));
265 :
266 0 : ctx->shutdown = 1;
267 0 : } else {
268 0 : int fired = fd_fseq_query( ctx->waker_fseq )==1UL;
269 0 : if( FD_LIKELY( fired ) ) fd_fseq_update( ctx->waker_fseq, 0UL );
270 0 : uchar * buffer;
271 0 : ulong buffer_sz;
272 0 : fd_ip4_port_t peer;
273 0 : int result = fd_genesis_client_poll( ctx->client, fd_clock_tile_now( ctx->clock ), &peer, &buffer, &buffer_sz, charge_busy );
274 0 : if( FD_LIKELY( fired ) ) fd_waker_client_rearm( ctx->waker_client_idx );
275 0 : if( FD_UNLIKELY( -1==result ) ) FD_LOG_ERR(( "failed to retrieve genesis.bin from any configured gossip entrypoints" ));
276 0 : if( FD_LIKELY( 1==result ) ) return;
277 :
278 0 : uchar * decompressed = ctx->genesis_blob;
279 0 : ulong actual_decompressed_sz = 0UL;
280 0 : bz_stream bzstrm = {0};
281 0 : bzstrm.bzalloc = bz2_malloc;
282 0 : bzstrm.bzfree = bz2_free;
283 0 : bzstrm.opaque = ctx->bz2_alloc;
284 0 : int bzerr = BZ2_bzDecompressInit( &bzstrm, 0, 0 );
285 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzDecompressInit() failed (%d)", bzerr ));
286 :
287 0 : ulong decompressed_sz = ctx->max_message_size + 4UL*FD_TAR_BLOCK_SZ;
288 :
289 0 : bzstrm.next_in = (char *)buffer;
290 0 : bzstrm.avail_in = (uint)buffer_sz;
291 0 : bzstrm.next_out = (char *)decompressed;
292 0 : bzstrm.avail_out = (uint)decompressed_sz;
293 0 : bzerr = BZ2_bzDecompress( &bzstrm );
294 : /* genesis.bin is the first archive entry. Stop after filling the
295 : bounded prefix buffer instead of retaining the trailing RocksDB
296 : entries included by Agave. */
297 0 : if( FD_UNLIKELY( BZ_STREAM_END!=bzerr && !(BZ_OK==bzerr && !bzstrm.avail_out) ) ) {
298 0 : FD_LOG_ERR(( "BZ2_bzDecompress() failed or input was truncated (%d)", bzerr ));
299 0 : }
300 :
301 0 : actual_decompressed_sz = decompressed_sz - (ulong)bzstrm.avail_out;
302 :
303 0 : bzerr = BZ2_bzDecompressEnd( &bzstrm );
304 0 : if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzDecompressEnd() failed (%d)", bzerr ));
305 :
306 0 : FD_TEST( actual_decompressed_sz>=FD_TAR_BLOCK_SZ );
307 :
308 0 : fd_tar_meta_t const * meta = (fd_tar_meta_t const *)decompressed;
309 0 : FD_TEST( !strcmp( meta->name, "genesis.bin" ) );
310 0 : uchar const * blob = decompressed+FD_TAR_BLOCK_SZ;
311 0 : ulong blob_sz = fd_tar_meta_get_size( meta );
312 0 : if( FD_UNLIKELY( blob_sz==ULONG_MAX ) ) {
313 0 : FD_LOG_ERR(( "Genesis blob from peer at `http://" FD_IP4_ADDR_FMT ":%hu` has invalid tar header size",
314 0 : FD_IP4_ADDR_FMT_ARGS( peer.addr ), fd_ushort_bswap( peer.port ) ));
315 0 : }
316 0 : if( FD_UNLIKELY( blob_sz>ctx->max_message_size ) ) {
317 0 : FD_LOG_ERR(( "Genesis blob from peer at `http://" FD_IP4_ADDR_FMT ":%hu` exceeds maximum size (blob_sz=%lu max=%lu)",
318 0 : FD_IP4_ADDR_FMT_ARGS( peer.addr ), fd_ushort_bswap( peer.port ), blob_sz, ctx->max_message_size ));
319 0 : }
320 0 : FD_TEST( actual_decompressed_sz>=fd_ulong_sat_add( FD_TAR_BLOCK_SZ, blob_sz ) );
321 :
322 0 : fd_hash_t hash[1];
323 0 : fd_sha256_hash( blob, blob_sz, hash->uc );
324 :
325 : /* Can't verify expected_shred_version here because it needs to be
326 : mixed in with hard_forks from the snapshot. Replay tile will
327 : combine them and do this verification. */
328 :
329 0 : if( FD_LIKELY( ctx->has_expected_genesis_hash && memcmp( hash, ctx->expected_genesis_hash, 32UL ) ) ) {
330 0 : FD_BASE58_ENCODE_32_BYTES( ctx->expected_genesis_hash, expected_genesis_hash_b58 );
331 0 : FD_BASE58_ENCODE_32_BYTES( hash->uc, hash_b58 );
332 0 : FD_LOG_ERR(( "An expected genesis hash of `%s` has been set in your configuration file at [consensus.expected_genesis_hash] "
333 0 : "but the genesis hash derived from the peer at `http://" FD_IP4_ADDR_FMT ":%hu` has unexpected hash `%s`",
334 0 : expected_genesis_hash_b58, FD_IP4_ADDR_FMT_ARGS( peer.addr ), fd_ushort_bswap( peer.port ), hash_b58 ));
335 0 : }
336 :
337 0 : FD_TEST( !ctx->bootstrap );
338 :
339 0 : fd_genesis_t * genesis = fd_genesis_parse( ctx->genesis, blob, blob_sz );
340 0 : if( FD_UNLIKELY( !genesis ) ) {
341 0 : FD_LOG_ERR(( "unable to decode downloaded solana genesis file due to violated hardcoded limits" ));
342 0 : }
343 :
344 0 : if( FD_LIKELY( ctx->validate_genesis_hash ) ) {
345 0 : verify_cluster_type( genesis, hash, ctx->genesis_path );
346 0 : }
347 :
348 0 : ulong msg_sz = sizeof(fd_genesis_meta_t) + blob_sz;
349 0 : if( FD_UNLIKELY( msg_sz>fd_genesi_tile_mtu( ctx->max_message_size ) ) ) {
350 0 : FD_LOG_ERR(( "The genesis blob downloaded from peer at `http://" FD_IP4_ADDR_FMT ":%hu` is too large for this Firedancer configuration (msg_sz=%lu max=%lu).\n"
351 0 : "Cannot start Firedancer. Please use a different genesis config or increase `development.genesis.max_file_size_mib`.",
352 0 : FD_IP4_ADDR_FMT_ARGS( peer.addr ), fd_ushort_bswap( peer.port ), msg_sz, fd_genesi_tile_mtu( ctx->max_message_size ) ));
353 0 : }
354 :
355 0 : fd_genesis_meta_t * dst = fd_chunk_to_laddr( ctx->out.mem, ctx->out.chunk0 );
356 0 : memset( dst, 0, sizeof(fd_genesis_meta_t) );
357 0 : dst->creation_time_seconds = genesis->creation_time;
358 0 : dst->genesis_hash = *hash;
359 :
360 0 : uchar * dst_blob = (uchar *)( dst+1 );
361 0 : dst->blob_sz = blob_sz;
362 0 : fd_memcpy( dst_blob, blob, blob_sz );
363 :
364 0 : fd_stem_publish( stem, 0UL, msg_sz, ctx->out.chunk0, 0UL, 0UL, 0UL, 0UL );
365 :
366 0 : ulong bytes_written = 0UL;
367 0 : while( bytes_written<blob_sz ) {
368 0 : long result = write( ctx->out_fd, blob+bytes_written, blob_sz-bytes_written );
369 0 : if( FD_UNLIKELY( -1==result ) ) FD_LOG_ERR(( "write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
370 0 : bytes_written += (ulong)result;
371 0 : }
372 :
373 0 : char basename[ PATH_MAX ];
374 0 : const char * last_slash = strrchr( ctx->genesis_path, '/' );
375 0 : if( FD_LIKELY( last_slash ) ) FD_TEST( fd_cstr_printf_check( basename, PATH_MAX, NULL, "%s", last_slash+1UL ) );
376 0 : else FD_TEST( fd_cstr_printf_check( basename, PATH_MAX, NULL, "%s", ctx->genesis_path ) );
377 :
378 0 : char basename_partial[ PATH_MAX ];
379 0 : FD_TEST( fd_cstr_printf_check( basename_partial, PATH_MAX, NULL, "%s.partial", basename ) );
380 :
381 0 : int err = (int)syscall( SYS_renameat2, ctx->out_dir_fd, basename_partial, ctx->out_dir_fd, basename, RENAME_NOREPLACE );
382 0 : if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "renameat2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
383 :
384 0 : FD_LOG_NOTICE(( "retrieved genesis %s%s%s from peer at %shttp://" FD_IP4_ADDR_FMT ":%hu/genesis.tar.bz2%s",
385 0 : fd_log_style_dim(), ctx->genesis_path, fd_log_style_normal(),
386 0 : fd_log_style_dim(), FD_IP4_ADDR_FMT_ARGS( peer.addr ), fd_ushort_bswap( peer.port ), fd_log_style_normal() ));
387 :
388 0 : ctx->shutdown = 1;
389 0 : }
390 0 : }
391 :
392 : static void
393 : process_local_genesis( fd_genesi_tile_t * ctx,
394 0 : char const * genesis_path ) {
395 0 : ctx->genesis_blob_sz = 0UL;
396 0 : for(;;) {
397 0 : if( FD_UNLIKELY( ctx->genesis_blob_sz==ctx->max_message_size ) ) {
398 0 : uchar extra;
399 0 : long result = read( ctx->in_fd, &extra, 1UL );
400 0 : if( FD_UNLIKELY( -1==result ) ) FD_LOG_ERR(( "read() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
401 0 : if( FD_UNLIKELY( result ) ) {
402 0 : FD_LOG_ERR(( "The genesis file at `%s` exceeds `development.genesis.max_file_size_mib` (%lu MiB).",
403 0 : genesis_path, ctx->max_message_size>>20 ));
404 0 : }
405 0 : break;
406 0 : }
407 0 : long result = read( ctx->in_fd, ctx->genesis_blob+ctx->genesis_blob_sz, ctx->max_message_size-ctx->genesis_blob_sz );
408 0 : if( FD_UNLIKELY( -1==result ) ) FD_LOG_ERR(( "read() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
409 0 : if( FD_UNLIKELY( !result ) ) break;
410 0 : ctx->genesis_blob_sz += (ulong)result;
411 0 : }
412 :
413 0 : if( FD_UNLIKELY( -1==close( ctx->in_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
414 :
415 0 : fd_genesis_t * genesis = fd_genesis_parse( ctx->genesis, ctx->genesis_blob, ctx->genesis_blob_sz );
416 0 : if( FD_UNLIKELY( !genesis ) ) {
417 0 : FD_LOG_ERR(( "unable to decode solana genesis from local file due to violated hardcoded limits" ));
418 0 : }
419 :
420 0 : fd_sha256_hash( ctx->genesis_blob, ctx->genesis_blob_sz, ctx->genesis_hash );
421 0 : if( FD_LIKELY( ctx->validate_genesis_hash ) ) {
422 0 : verify_cluster_type( genesis, ctx->genesis_hash, genesis_path );
423 0 : }
424 :
425 0 : if( FD_UNLIKELY( ctx->bootstrap && ctx->expected_shred_version ) ) {
426 0 : ushort xor = 0;
427 0 : for( ulong i=0UL; i<16UL; i++ ) xor ^= ctx->genesis_hash->us[ i ];
428 :
429 0 : xor = fd_ushort_bswap( xor );
430 0 : xor = fd_ushort_if( xor<USHORT_MAX, (ushort)(xor + 1), USHORT_MAX );
431 :
432 0 : FD_TEST( xor );
433 :
434 0 : if( FD_UNLIKELY( xor!=ctx->expected_shred_version ) ) {
435 0 : FD_LOG_ERR(( "This node is bootstrapping the cluster as it has no gossip entrypoints provided, but "
436 0 : "a [consensus.expected_shred_version] of %hu is provided which does not match the shred "
437 0 : "version of %hu computed from the genesis.bin file at `%s`",
438 0 : ctx->expected_shred_version, xor, genesis_path ));
439 0 : }
440 0 : }
441 :
442 0 : if( FD_LIKELY( ctx->has_expected_genesis_hash && memcmp( ctx->genesis_hash, ctx->expected_genesis_hash, 32UL ) ) ) {
443 0 : FD_BASE58_ENCODE_32_BYTES( ctx->expected_genesis_hash, expected_genesis_hash_b58 );
444 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
445 0 : FD_LOG_ERR(( "An expected genesis hash of %s%s%s has been set in your configuration file at [consensus.expected_genesis_hash] "
446 0 : "but the genesis hash derived from the genesis file at %s%s%s has unexpected hash %s%s%s",
447 0 : fd_log_style_bold(), expected_genesis_hash_b58, fd_log_style_normal(),
448 0 : fd_log_style_dim(), genesis_path, fd_log_style_normal(),
449 0 : fd_log_style_bold(), genesis_hash_b58, fd_log_style_normal() ));
450 0 : }
451 0 : }
452 :
453 : static void
454 : privileged_init( fd_topo_t const * topo,
455 0 : fd_topo_tile_t const * tile ) {
456 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
457 :
458 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
459 0 : fd_genesi_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_genesi_tile_t ), sizeof( fd_genesi_tile_t ) );
460 0 : fd_genesis_client_t * _client = FD_SCRATCH_ALLOC_APPEND( l, fd_genesis_client_align(), fd_genesis_client_footprint() );
461 :
462 0 : fd_memset( ctx, 0, sizeof( fd_genesi_tile_t ) );
463 :
464 0 : ctx->local_genesis = 1;
465 0 : ctx->in_fd = open( tile->genesi.genesis_path, O_RDONLY|O_CLOEXEC );
466 0 : if( FD_UNLIKELY( -1==ctx->in_fd ) ) {
467 0 : if( FD_LIKELY( errno==ENOENT ) ) {
468 0 : FD_LOG_INFO(( "no local genesis.bin file found at `%s`", tile->genesi.genesis_path ));
469 :
470 0 : if( FD_UNLIKELY( !tile->genesi.entrypoints_cnt ) ) {
471 0 : FD_LOG_ERR(( "This node is bootstrapping the cluster as it has no gossip entrypoints provided, but "
472 0 : "the genesis.bin file at `%s` does not exist. Please provide a valid genesis.bin "
473 0 : "file by running genesis, or join an existing cluster.",
474 0 : tile->genesi.genesis_path ));
475 0 : } else {
476 0 : if( FD_UNLIKELY( !tile->genesi.allow_download ) ) {
477 0 : FD_LOG_ERR(( "There is no genesis.bin file at `%s` and automatic downloading is disabled as "
478 0 : "genesis_download is false in your configuration file. Please either provide a valid "
479 0 : "genesis.bin file locally, or allow downloading from a gossip entrypoint.",
480 0 : tile->genesi.genesis_path ));
481 0 : } else {
482 0 : char basename[ PATH_MAX ];
483 0 : fd_cstr_ncpy( basename, tile->genesi.genesis_path, PATH_MAX );
484 0 : char * last_slash = strrchr( basename, '/' );
485 0 : if( FD_LIKELY( last_slash ) ) *last_slash = '\0';
486 :
487 0 : ctx->out_dir_fd = open( basename, O_RDONLY|O_CLOEXEC|O_DIRECTORY );
488 0 : if( FD_UNLIKELY( -1==ctx->out_dir_fd ) ) FD_LOG_ERR(( "open() failed for genesis dir `%s` (%i-%s)", basename, errno, fd_io_strerror( errno ) ));
489 :
490 : /* Switch to non-root uid/gid for file creation. Permissions checks
491 : are still done as root. */
492 0 : gid_t gid = getgid();
493 0 : uid_t uid = getuid();
494 0 : if( FD_LIKELY( !gid && -1==syscall( __NR_setresgid, -1, tile->genesi.target_gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
495 0 : if( FD_LIKELY( !uid && -1==syscall( __NR_setresuid, -1, tile->genesi.target_uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
496 :
497 0 : char const * genesis_basename = strrchr( tile->genesi.genesis_path, '/' );
498 0 : genesis_basename = genesis_basename ? genesis_basename+1UL : tile->genesi.genesis_path;
499 :
500 0 : char partialname[ PATH_MAX ];
501 0 : FD_TEST( fd_cstr_printf_check( partialname, PATH_MAX, NULL, "%s.partial", genesis_basename ) );
502 0 : ctx->out_fd = openat( ctx->out_dir_fd, partialname, O_CREAT|O_WRONLY|O_CLOEXEC|O_TRUNC, S_IRUSR|S_IWUSR );
503 0 : if( FD_UNLIKELY( -1==ctx->out_fd ) ) FD_LOG_ERR(( "openat() failed for genesis file `%s.partial` (%i-%s)", tile->genesi.genesis_path, errno, fd_io_strerror( errno ) ));
504 :
505 0 : if( FD_UNLIKELY( -1==syscall( __NR_setresuid, -1, uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
506 0 : if( FD_UNLIKELY( -1==syscall( __NR_setresgid, -1, gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
507 :
508 0 : ctx->local_genesis = 0;
509 0 : ctx->client = fd_genesis_client_join( fd_genesis_client_new( _client ) );
510 0 : FD_TEST( ctx->client );
511 :
512 0 : fd_dns_resolve_peers( tile->genesi.entrypoints[ 0 ], sizeof(tile->genesi.entrypoints[ 0 ]), tile->genesi.entrypoints_cnt, "gossip.entrypoints", ctx->entrypoints );
513 0 : ctx->entrypoints_cnt = tile->genesi.entrypoints_cnt;
514 :
515 0 : fd_genesis_client_init( ctx->client, ctx->entrypoints, ctx->entrypoints_cnt, FD_WAKER_INNER_FD( tile->waker_client_idx ) );
516 0 : }
517 0 : }
518 0 : } else {
519 0 : FD_LOG_ERR(( "could not open genesis.bin file at `%s` (%i-%s)", tile->genesi.genesis_path, errno, fd_io_strerror( errno ) ));
520 0 : }
521 0 : }
522 0 : }
523 :
524 : static void
525 : unprivileged_init( fd_topo_t const * topo,
526 0 : fd_topo_tile_t const * tile ) {
527 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
528 :
529 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
530 0 : fd_genesi_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_genesi_tile_t ), sizeof( fd_genesi_tile_t ) );
531 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_genesis_client_align(), fd_genesis_client_footprint() );
532 0 : void * _alloc = FD_SCRATCH_ALLOC_APPEND( l, fd_alloc_align(), fd_alloc_footprint() );
533 0 : void * _accdb = !tile->genesi.entrypoints_cnt ?
534 0 : FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->genesi.max_live_slots, 0 ) ) :
535 0 : NULL;
536 0 : void * _genesis = FD_SCRATCH_ALLOC_APPEND( l, fd_genesis_align(), fd_genesis_footprint( fd_genesis_account_max( tile->genesi.max_message_size ) ) );
537 0 : void * _genesis_blob = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar), tile->genesi.max_message_size + 4UL*FD_TAR_BLOCK_SZ );
538 :
539 0 : fd_lthash_zero( ctx->lthash );
540 :
541 0 : ctx->waker_client_idx = tile->waker_client_idx;
542 0 : FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
543 0 : ctx->waker_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->waker_fseq_obj_id ) );
544 0 : FD_TEST( ctx->waker_fseq );
545 0 : fd_clock_tile_init( ctx->clock );
546 :
547 0 : ctx->genesis = fd_genesis_new( _genesis, fd_genesis_account_max( tile->genesi.max_message_size ) );
548 0 : FD_TEST( ctx->genesis );
549 0 : ctx->genesis_blob = _genesis_blob;
550 0 : ctx->max_message_size = tile->genesi.max_message_size;
551 0 : ctx->shutdown = 0;
552 0 : ctx->bootstrap = !tile->genesi.entrypoints_cnt;
553 0 : ctx->expected_shred_version = tile->genesi.expected_shred_version;
554 0 : ctx->has_expected_genesis_hash = tile->genesi.has_expected_genesis_hash;
555 0 : ctx->validate_genesis_hash = tile->genesi.validate_genesis_hash;
556 0 : fd_memcpy( ctx->expected_genesis_hash, tile->genesi.expected_genesis_hash, 32UL );
557 :
558 0 : ctx->accdb = NULL;
559 0 : if( FD_UNLIKELY( ctx->bootstrap ) ) {
560 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->genesi.accdb_obj_id );
561 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
562 0 : FD_TEST( accdb_shmem );
563 0 : fd_sleep_t * accdb_sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL;
564 0 : ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL, accdb_sleep, fd_topo_find_tile( topo, "accdb", 0UL ), 0 ) );
565 0 : FD_TEST( ctx->accdb );
566 0 : }
567 :
568 0 : if( FD_LIKELY( -1!=ctx->in_fd ) ) {
569 0 : process_local_genesis( ctx, tile->genesi.genesis_path );
570 0 : if( FD_UNLIKELY( ctx->bootstrap ) ) {
571 0 : ctx->accdb_root = initialize_accdb( ctx->accdb, ctx->genesis, ctx->genesis_blob, ctx->lthash );
572 0 : fd_accdb_flush_metrics( ctx->accdb );
573 0 : }
574 0 : }
575 :
576 0 : FD_TEST( fd_cstr_printf_check( ctx->genesis_path, PATH_MAX, NULL, "%s", tile->genesi.genesis_path ) );
577 :
578 0 : FD_TEST( tile->out_cnt==1UL );
579 0 : fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ 0 ] ];
580 0 : FD_TEST( out_link->depth==1UL ); /* buffer holds a single message (dcache not a ring buffer) */
581 0 : FD_TEST( out_link->mtu>=fd_genesi_tile_mtu( tile->genesi.max_message_size ) );
582 0 : ctx->out.mem = fd_wksp_containing( out_link->dcache );
583 0 : ctx->out.chunk0 = fd_dcache_compact_chunk0( ctx->out.mem, out_link->dcache );
584 :
585 0 : ctx->bz2_alloc = fd_alloc_join( fd_alloc_new( _alloc, 1UL ), 1UL );
586 0 : FD_TEST( ctx->bz2_alloc );
587 :
588 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
589 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
590 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
591 0 : }
592 :
593 : static ulong
594 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
595 0 : fd_topo_tile_t const * tile ) {
596 0 : return 1UL + /* stderr */
597 0 : 1UL + /* logfile */
598 0 : 1UL + /* genesis file */
599 0 : 1UL + /* genesis dir */
600 0 : tile->genesi.entrypoints_cnt; /* for the client */
601 0 : }
602 :
603 : static ulong
604 : populate_allowed_seccomp( fd_topo_t const * topo,
605 : fd_topo_tile_t const * tile,
606 : ulong out_cnt,
607 0 : struct sock_filter * out ) {
608 :
609 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
610 :
611 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
612 0 : fd_genesi_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_genesi_tile_t ), sizeof( fd_genesi_tile_t ) );
613 :
614 0 : uint in_fd, out_fd, out_dir_fd;
615 0 : if( FD_LIKELY( -1!=ctx->in_fd ) ) {
616 0 : in_fd = (uint)ctx->in_fd;
617 0 : out_fd = (uint)-1;
618 0 : out_dir_fd = (uint)-1;
619 0 : } else {
620 0 : in_fd = (uint)-1;
621 0 : out_fd = (uint)ctx->out_fd;
622 0 : out_dir_fd = (uint)ctx->out_dir_fd;
623 0 : }
624 :
625 0 : uint accounts_fd = !tile->genesi.entrypoints_cnt ? (uint)FD_ACCDB_FD_RW : (uint)-1;
626 0 : uint epoll_inner_fd = (uint)FD_WAKER_INNER_FD( tile->waker_client_idx );
627 0 : uint epoll_outer_fd = (uint)FD_WAKER_OUTER_FD;
628 0 : populate_sock_filter_policy_fd_genesi_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), in_fd, out_fd, out_dir_fd, accounts_fd, epoll_inner_fd, epoll_outer_fd );
629 0 : return sock_filter_policy_fd_genesi_tile_instr_cnt;
630 0 : }
631 :
632 : static ulong
633 : populate_allowed_fds( fd_topo_t const * topo,
634 : fd_topo_tile_t const * tile,
635 : ulong out_fds_cnt,
636 0 : int * out_fds ) {
637 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
638 :
639 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
640 0 : fd_genesi_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_genesi_tile_t ), sizeof( fd_genesi_tile_t ) );
641 :
642 0 : if( FD_UNLIKELY( out_fds_cnt<tile->genesi.entrypoints_cnt+8UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
643 :
644 0 : ulong out_cnt = 0UL;
645 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
646 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
647 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
648 0 : out_fds[ out_cnt++ ] = FD_WAKER_OUTER_FD; /* waker outer epoll fd (rearm) */
649 0 : out_fds[ out_cnt++ ] = FD_WAKER_INNER_FD( tile->waker_client_idx ); /* waker inner epoll fd */
650 0 : if( FD_UNLIKELY( !tile->genesi.entrypoints_cnt ) )
651 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
652 :
653 0 : if( FD_UNLIKELY( -1==ctx->in_fd ) ) {
654 0 : FD_TEST( -1!=ctx->out_dir_fd );
655 0 : FD_TEST( -1!=ctx->out_fd );
656 0 : out_fds[ out_cnt++ ] = ctx->out_dir_fd;
657 0 : out_fds[ out_cnt++ ] = ctx->out_fd;
658 :
659 0 : for( ulong i=0UL; i<tile->genesi.entrypoints_cnt; i++ ) {
660 0 : int fd = fd_genesis_client_get_pollfds( ctx->client )[ i ].fd;
661 0 : if( FD_LIKELY( -1!=fd ) ) out_fds[ out_cnt++ ] = fd;
662 0 : }
663 0 : } else {
664 0 : FD_TEST( -1!=ctx->in_fd );
665 0 : out_fds[ out_cnt++ ] = ctx->in_fd;
666 0 : }
667 :
668 0 : return out_cnt;
669 0 : }
670 :
671 0 : #define STEM_LAZY ((long)1e5) /* 0.1ms */
672 0 : #define STEM_BURST (1UL)
673 :
674 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_genesi_tile_t
675 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_genesi_tile_t)
676 :
677 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
678 0 : #define STEM_CALLBACK_NEXT_DEADLINE next_deadline
679 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
680 0 : #define STEM_CALLBACK_SHOULD_SHUTDOWN should_shutdown
681 :
682 : #include "../../disco/stem/fd_stem.c"
683 :
684 : static ulong
685 0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
686 0 : return sizeof(fd_event_accdb_partition_added_t);
687 0 : }
688 :
689 : fd_topo_run_tile_t fd_tile_genesi = {
690 : .name = "genesi",
691 : .rlimit_file_cnt_fn = rlimit_file_cnt,
692 : .allow_connect = 1,
693 : .allow_renameat = 1,
694 : .populate_allowed_seccomp = populate_allowed_seccomp,
695 : .populate_allowed_fds = populate_allowed_fds,
696 : .loose_footprint = loose_footprint,
697 : .scratch_align = scratch_align,
698 : .scratch_footprint = scratch_footprint,
699 : .privileged_init = privileged_init,
700 : .unprivileged_init = unprivileged_init,
701 : .max_event_sz = max_event_sz,
702 : .run = stem_run,
703 : };
|