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