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