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