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