Line data Source code
1 : #define _GNU_SOURCE
2 : #include "../../fdctl/configure/configure.h"
3 :
4 : #include "../../../ballet/shred/fd_shred.h"
5 : #include "../../../disco/shred/fd_shredder.h"
6 : #include "../../../ballet/poh/fd_poh.h"
7 : #include "../../../disco/tiles.h"
8 : #include "../genesis_hash.h"
9 :
10 : #include <sys/stat.h>
11 : #include <sys/wait.h>
12 : #include <dirent.h>
13 :
14 : #define NAME "blockstore"
15 :
16 : extern void
17 : fd_ext_blockstore_create_block0( char const * ledger_path,
18 : ulong shred_cnt,
19 : uchar const * shred_bytes,
20 : ulong shred_sz,
21 : ulong stride );
22 :
23 0 : static inline void zero_signer( void * _1, uchar * sig, uchar const * _2 ) { (void)_1; (void)_2; memset( sig, '\0', 64UL ); }
24 :
25 : static void
26 0 : init( config_t * const config ) {
27 : /* The Agave validator cannot boot without a block 0 existing in the
28 : blockstore in the ledger directory, so we have to create one. This
29 : creates a directory "rocksdb" under which the blockstore with
30 : block0 is created. The entire directory should be included in the
31 : genesis archive "genesis.tar.bz2". */
32 :
33 : /* TODO: Parse genesis.bin and use those values instead. */
34 0 : ulong ticks_per_slot = config->development.genesis.ticks_per_slot;
35 0 : ulong hashes_per_tick = config->development.genesis.hashes_per_tick;
36 :
37 0 : char genesis_path[ PATH_MAX ];
38 0 : FD_TEST( fd_cstr_printf_check( genesis_path, PATH_MAX, NULL, "%s/genesis.bin", config->ledger.path ) );
39 0 : uchar genesis_hash[ 32 ] = { 0 };
40 0 : ushort shred_version = compute_shred_version( genesis_path, genesis_hash );
41 :
42 : /* This is not a fundamental limit. It could be set as high as 663
43 : with no modifications to the rest of the code. It's set to 128
44 : because that seems more than enough and we don't need to consume
45 : that much stack space. It could be set even higher if you add
46 : multiple FEC sets. */
47 0 : #define GENESIS_MAX_TICKS_PER_SLOT 128UL
48 0 : FD_TEST( ticks_per_slot<GENESIS_MAX_TICKS_PER_SLOT );
49 0 : struct {
50 0 : ulong ticks_in_batch;
51 0 : fd_entry_batch_header_t ticks[ GENESIS_MAX_TICKS_PER_SLOT ];
52 0 : } batch;
53 :
54 0 : batch.ticks_in_batch = ticks_per_slot;
55 0 : uchar poh_hash[ 32 ] = {0};
56 0 : memcpy( poh_hash, genesis_hash, 32UL );
57 :
58 0 : for( ulong i=0UL; i<ticks_per_slot; i++ ) {
59 0 : fd_poh_append( poh_hash, hashes_per_tick );
60 :
61 0 : batch.ticks[ i ].hashcnt_delta = hashes_per_tick;
62 0 : batch.ticks[ i ].txn_cnt = 0UL;
63 0 : memcpy( batch.ticks[ i ].hash, poh_hash, 32UL );
64 0 : }
65 :
66 0 : ulong batch_sz = sizeof(ulong)+ticks_per_slot*sizeof(fd_entry_batch_header_t);
67 :
68 0 : FD_TEST( fd_shredder_count_data_shreds ( batch_sz )<=34UL );
69 0 : FD_TEST( fd_shredder_count_parity_shreds( batch_sz )<=34UL );
70 :
71 0 : fd_shred34_t data, parity;
72 0 : fd_fec_set_t fec;
73 0 : for( ulong i=0UL; i<34UL; i++ ) {
74 0 : fec.data_shreds [ i ] = data.pkts [ i ].buffer;
75 0 : fec.parity_shreds[ i ] = parity.pkts[ i ].buffer;
76 0 : }
77 0 : for( ulong i=34UL; i<FD_REEDSOL_DATA_SHREDS_MAX; i++ ) fec.data_shreds [ i ] = NULL;
78 0 : for( ulong i=34UL; i<FD_REEDSOL_PARITY_SHREDS_MAX; i++ ) fec.parity_shreds[ i ] = NULL;
79 :
80 0 : fd_entry_batch_meta_t meta[ 1 ] = {{
81 0 : .parent_offset = 0UL,
82 0 : .reference_tick = ticks_per_slot,
83 0 : .block_complete = 1
84 0 : }};
85 :
86 0 : fd_shredder_t _shredder[ 1 ];
87 0 : fd_shredder_t * shredder = fd_shredder_join( fd_shredder_new( _shredder, zero_signer, NULL, shred_version ) );
88 :
89 0 : fd_shredder_init_batch( shredder, &batch, batch_sz, 0UL, meta );
90 0 : fd_shredder_next_fec_set( shredder, &fec );
91 :
92 : /* Fork off a new process for inserting the shreds to the blockstore.
93 : RocksDB creates a dozen background workers, and doesn't close them
94 : by default. This would cause problems for sandboxing because you
95 : can't unshare a user namespace once multi-threaded, so just do it
96 : in another process that we can then terminate. */
97 :
98 0 : pid_t pid = fork();
99 0 : if( FD_UNLIKELY( pid == -1 ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
100 :
101 0 : if( FD_LIKELY( !pid ) ) {
102 : /* Switch to target user in the configuration when creating the
103 : genesis.bin file so it is permissioned correctly. */
104 0 : gid_t gid = getgid();
105 0 : uid_t uid = getuid();
106 0 : if( FD_LIKELY( gid == 0 && setegid( config->gid ) ) )
107 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
108 0 : if( FD_LIKELY( uid == 0 && seteuid( config->uid ) ) )
109 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
110 :
111 0 : umask( S_IRWXO | S_IRWXG );
112 :
113 0 : fd_ext_blockstore_create_block0( config->ledger.path, fec.data_shred_cnt, (uchar const *)data.pkts, FD_SHRED_MIN_SZ, FD_SHRED_MAX_SZ );
114 :
115 0 : exit_group( 0 );
116 0 : } else {
117 0 : int wstatus;
118 0 : if( FD_UNLIKELY( waitpid( pid, &wstatus, 0 )==-1 ) ) FD_LOG_ERR(( "waitpid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
119 0 : if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) )
120 0 : FD_LOG_ERR(( "genesis blockstore creation process terminated by signal %i-%s", WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
121 0 : if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) )
122 0 : FD_LOG_ERR(( "genesis blockstore creation process exited with status %i", WEXITSTATUS( wstatus ) ));
123 0 : }
124 :
125 0 : }
126 :
127 : static void
128 : fini( config_t * const config,
129 0 : int pre_init ) {
130 0 : (void)pre_init;
131 :
132 0 : DIR * dir = opendir( config->ledger.path );
133 0 : if( FD_UNLIKELY( !dir ) ) {
134 0 : if( errno == ENOENT ) return;
135 0 : FD_LOG_ERR(( "opendir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
136 0 : }
137 :
138 0 : struct dirent * entry;
139 0 : errno = 0;
140 0 : while(( entry = readdir( dir ) )) {
141 0 : if( FD_LIKELY( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) ) ) continue;
142 :
143 : /* genesis.bin managed by genesis stage*/
144 0 : if( FD_LIKELY( !strcmp( entry->d_name, "genesis.bin" ) ) ) continue;
145 :
146 0 : char path1[ PATH_MAX ];
147 0 : FD_TEST( fd_cstr_printf_check( path1, PATH_MAX, NULL, "%s/%s", config->ledger.path, entry->d_name ) );
148 :
149 0 : struct stat st;
150 0 : if( FD_UNLIKELY( lstat( path1, &st ) ) ) {
151 0 : if( FD_LIKELY( errno == ENOENT ) ) continue;
152 0 : FD_LOG_ERR(( "stat `%s` failed (%i-%s)", path1, errno, fd_io_strerror( errno ) ));
153 0 : }
154 :
155 0 : if( FD_UNLIKELY( S_ISDIR( st.st_mode ) ) ) {
156 0 : rmtree( path1, 1 );
157 0 : } else {
158 0 : if( FD_UNLIKELY( unlink( path1 ) && errno != ENOENT ) )
159 0 : FD_LOG_ERR(( "unlink `%s` failed (%i-%s)", path1, errno, fd_io_strerror( errno ) ));
160 0 : }
161 0 : }
162 :
163 0 : if( FD_UNLIKELY( errno && errno!=ENOENT ) ) FD_LOG_ERR(( "readdir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
164 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
165 0 : }
166 :
167 : static configure_result_t
168 0 : check( config_t * const config ) {
169 0 : int has_non_genesis = 0;
170 :
171 0 : DIR * dir = opendir( config->ledger.path );
172 0 : if( FD_UNLIKELY( !dir ) ) {
173 0 : if( FD_UNLIKELY( errno==ENOENT ) ) NOT_CONFIGURED( "ledger directory does not exist at `%s`", config->ledger.path );
174 0 : FD_LOG_ERR(( "opendir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
175 0 : }
176 :
177 0 : struct dirent * entry;
178 0 : errno = 0;
179 0 : while(( entry = readdir( dir ) )) {
180 0 : if( FD_LIKELY( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) ) ) continue;
181 :
182 : /* genesis.bin managed by genesis stage*/
183 0 : if( FD_LIKELY( !strcmp( entry->d_name, "genesis.bin" ) ) ) continue;
184 0 : if( FD_LIKELY( !strcmp( entry->d_name, "rocksdb" ) ) ) {
185 0 : char rocksdb_path[ PATH_MAX ];
186 0 : fd_cstr_printf_check( rocksdb_path, PATH_MAX, NULL, "%s/rocksdb", config->ledger.path );
187 :
188 0 : configure_result_t result = check_dir( rocksdb_path, config->uid, config->gid, S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR );
189 0 : if( FD_UNLIKELY( result.result != CONFIGURE_OK ) ) {
190 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
191 0 : return result;
192 0 : }
193 0 : }
194 :
195 0 : has_non_genesis = 1;
196 0 : break;
197 0 : }
198 :
199 0 : if( FD_UNLIKELY( errno && errno!=ENOENT ) ) FD_LOG_ERR(( "readdir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
200 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir `%s` failed (%i-%s)", config->ledger.path, errno, fd_io_strerror( errno ) ));
201 :
202 0 : if( FD_LIKELY( has_non_genesis ) ) {
203 0 : PARTIALLY_CONFIGURED( "rocksdb directory exists at `%s`", config->ledger.path );
204 0 : } else {
205 0 : NOT_CONFIGURED( "rocksdb directory does not exist at `%s`", config->ledger.path );
206 0 : }
207 0 : }
208 :
209 : configure_stage_t blockstore = {
210 : .name = NAME,
211 : .always_recreate = 1,
212 : .init = init,
213 : .fini = fini,
214 : .check = check,
215 : };
216 :
217 : #undef NAME
|