Line data Source code
1 : #include "fd_accdb_fsck.h"
2 : #include "../runtime/fd_runtime_const.h"
3 : #include "../../ballet/lthash/fd_lthash_adder.h"
4 :
5 : #define VINYL_KEY_FMT "%016lx%016lx%016lx%016lx"
6 : #define VINYL_KEY_FMT_ARGS( key ) fd_ulong_bswap( (key).ul[0] ), fd_ulong_bswap( (key).ul[1] ), fd_ulong_bswap( (key).ul[2] ), fd_ulong_bswap( (key).ul[3] )
7 :
8 : /* meta_query_fast is a simplified version of fd_vinyl_meta_prepare */
9 :
10 : static fd_vinyl_meta_ele_t *
11 : meta_query_fast( fd_vinyl_meta_t * join,
12 : fd_vinyl_key_t const * key,
13 0 : ulong memo ) {
14 0 : fd_vinyl_meta_ele_t * ele0 = join->ele;
15 0 : ulong ele_max = join->ele_max;
16 0 : ulong probe_max = join->probe_max;
17 0 : void * ctx = join->ctx;
18 :
19 0 : ulong start_idx = memo & (ele_max-1UL);
20 :
21 0 : for(;;) {
22 0 : ulong ele_idx = start_idx;
23 0 : for( ulong probe_rem=probe_max; probe_rem; probe_rem-- ) {
24 0 : fd_vinyl_meta_ele_t * ele = ele0 + ele_idx;
25 0 : if( fd_vinyl_meta_private_ele_is_free( ctx, ele ) ) return NULL;
26 0 : if( fd_vinyl_key_eq( &ele->phdr.key, key ) ) {
27 0 : if( FD_UNLIKELY( ele->memo != memo ) ) FD_LOG_ERR(( "memo mismatch" ));
28 0 : return ele;
29 0 : }
30 0 : ele_idx = (ele_idx+1UL) & (ele_max-1UL);
31 0 : }
32 0 : return NULL;
33 0 : }
34 :
35 0 : FD_LOG_CRIT(( "unreachable" ));
36 0 : }
37 :
38 : uint
39 : fd_accdb_fsck_vinyl( fd_vinyl_io_t * io,
40 : fd_vinyl_meta_t * meta,
41 0 : uint flags ) {
42 0 : _Bool const lthash = !!( flags & FD_ACCDB_FSCK_FLAGS_LTHASH );
43 :
44 0 : uint err = FD_ACCDB_FSCK_NO_ERROR;
45 0 : ulong err_cnt = 0UL;
46 0 : ulong const err_max = 512UL;
47 :
48 : /* Join memory-mapped bstream */
49 :
50 0 : ulong const io_seed = fd_vinyl_io_seed( io );
51 0 : uchar const * const mmio = fd_vinyl_mmio ( io );
52 0 : ulong const mmio_sz = fd_vinyl_mmio_sz( io );
53 0 : ulong const seq_past = io->seq_past;
54 0 : ulong const seq_present = io->seq_present;
55 0 : ulong const dev_sz = mmio_sz;
56 0 : FD_LOG_INFO(( "FSCK starting ... seq_past=%lu seq_present=%lu mmio_sz=%lu",
57 0 : seq_past, seq_present, mmio_sz ));
58 : /* FIXME ASSUMING dev_sz==mmio_sz IS NOT VAILD ACCORDING TO DOCS */
59 :
60 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( seq_past, FD_VINYL_BSTREAM_BLOCK_SZ ) ) ) {
61 0 : FD_LOG_WARNING(( "misaligned seq_past (%lu)", seq_past ));
62 0 : return FD_ACCDB_FSCK_INVARIANT;
63 0 : }
64 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( seq_present, FD_VINYL_BSTREAM_BLOCK_SZ ) ) ) {
65 0 : FD_LOG_WARNING(( "misaligned seq_present (%lu)", seq_present ));
66 0 : return FD_ACCDB_FSCK_INVARIANT;
67 0 : }
68 0 : ulong dseq = seq_present - seq_past;
69 0 : if( FD_UNLIKELY( fd_vinyl_seq_gt( seq_past, seq_present ) || dseq>mmio_sz ) ) {
70 0 : FD_LOG_WARNING(( "invalid seq range [%lu,%lu) for mmio_sz %lu", seq_past, seq_present, mmio_sz ));
71 0 : return FD_ACCDB_FSCK_INVARIANT;
72 0 : }
73 :
74 : /* Phase 1: Scan meta map left-to-right. Verify the following:
75 : - memo (key hash correct?)
76 : - ctl (obviously incorrect meta entry?)
77 : - probe_max (key outside of probe range?)
78 : - query (is this key visible to queries? detect duplicate)
79 : Mark each element as not-visited. */
80 :
81 0 : ulong const meta_seed = meta->seed;
82 0 : fd_vinyl_meta_ele_t * const ele0 = meta->ele;
83 0 : ulong const ele_max = meta->ele_max;
84 :
85 0 : for( ulong i=0UL; i<ele_max; i++ ) {
86 0 : fd_vinyl_meta_ele_t * ele = &ele0[ i ];
87 0 : if( FD_LIKELY( fd_vinyl_meta_private_ele_is_free( meta->ctx, ele ) ) ) continue;
88 :
89 0 : ulong memo = fd_vinyl_key_memo( meta_seed, &ele->phdr.key );
90 0 : ulong val_esz = fd_vinyl_bstream_ctl_sz( ele->phdr.ctl );
91 :
92 0 : int bad_ctl = fd_vinyl_bstream_ctl_type ( ele->phdr.ctl )!=FD_VINYL_BSTREAM_CTL_TYPE_PAIR;
93 0 : int bad_style = fd_vinyl_bstream_ctl_style( ele->phdr.ctl )!=FD_VINYL_BSTREAM_CTL_STYLE_RAW;
94 0 : int bad_memo = memo != ele->memo;
95 0 : int bad_query = meta_query_fast( meta, &ele->phdr.key, ele->memo )!=ele;
96 0 : int bad_sz = val_esz > sizeof(fd_account_meta_t)+FD_RUNTIME_ACC_SZ_MAX;
97 0 : int bad_seq0 = fd_vinyl_seq_lt( ele->seq, seq_past ) | fd_vinyl_seq_ge( ele->seq, seq_present );
98 0 : int bad_seq1 = fd_vinyl_seq_gt( ele->seq+fd_vinyl_bstream_pair_sz( val_esz ), seq_present );
99 :
100 0 : if( FD_UNLIKELY( bad_ctl | bad_style | bad_memo | bad_query | bad_sz | bad_seq0 | bad_seq1 ) ) {
101 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: index corruption detected key=" VINYL_KEY_FMT " memo=%016lx meta_idx=%lu seq=%lu err=%s",
102 0 : VINYL_KEY_FMT_ARGS( ele->phdr.key ),
103 0 : memo,
104 0 : i,
105 0 : ele->seq,
106 0 : bad_ctl ? "bad ctl" :
107 0 : bad_style ? "bad style" :
108 0 : bad_memo ? "bad memo" :
109 0 : bad_query ? "bad query" :
110 0 : bad_sz ? "bad sz" :
111 0 : bad_seq0 ? "bad seq0" :
112 0 : "bad seq1" ));
113 0 : if( FD_UNLIKELY( ++err_cnt>=err_max ) ) {
114 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: too many errors, stopping" ));
115 0 : return FD_ACCDB_FSCK_UNKNOWN;
116 0 : }
117 0 : }
118 :
119 0 : ele->line_idx = ULONG_MAX; /* mark not visited */
120 0 : }
121 :
122 0 : if( !err_cnt ) FD_LOG_INFO(( "FSCK: meta OK" ));
123 :
124 : /* Phase 2: Scan bstream past-to-present. Mark meta elements as
125 : visited along the way. Verify that:
126 : - meta entries match bstream blocks
127 : - bstream block checksums are valid */
128 :
129 0 : fd_lthash_value_t sum[1]; fd_lthash_zero( sum );
130 0 : fd_lthash_adder_t adder_[1];
131 0 : fd_lthash_adder_t * adder = NULL;
132 0 : if( lthash ) {
133 0 : adder = fd_lthash_adder_new( adder_ );
134 0 : FD_TEST( adder );
135 0 : }
136 :
137 0 : ulong seq = seq_past;
138 0 : ulong seq_report = seq;
139 0 : while( seq<seq_present ) {
140 0 : if( FD_UNLIKELY( seq>=seq_report+(1UL<<30) ) ) {
141 0 : FD_LOG_INFO(( "FSCK progress: seq=%lu", seq ));
142 0 : seq_report = seq;
143 0 : }
144 :
145 : /* Map block to device */
146 0 : ulong mm_off = seq % dev_sz;
147 0 : ulong dev_off = mm_off+FD_VINYL_BSTREAM_BLOCK_SZ;
148 0 : if( FD_UNLIKELY( mm_off+FD_VINYL_BSTREAM_BLOCK_SZ > mmio_sz ) ) {
149 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream block crosses mmio boundary: seq=%lu dev_off=%lu", seq, dev_off ));
150 0 : return FD_ACCDB_FSCK_CORRUPT;
151 0 : }
152 :
153 : /* Interpret block */
154 0 : fd_vinyl_bstream_block_t block = FD_LOAD( fd_vinyl_bstream_block_t, mmio+mm_off );
155 0 : int ctl_type = fd_vinyl_bstream_ctl_type( block.ctl );
156 0 : switch( ctl_type ) {
157 :
158 0 : case FD_VINYL_BSTREAM_CTL_TYPE_PAIR: {
159 0 : ulong val_esz = fd_vinyl_bstream_ctl_sz( block.ctl );
160 0 : ulong block_sz = fd_vinyl_bstream_pair_sz( val_esz );
161 0 : if( FD_UNLIKELY( block_sz<FD_VINYL_BSTREAM_BLOCK_SZ ) ) {
162 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream pair has invalid block size (%lu) at seq=%lu dev_off=%lu", block_sz, seq, dev_off ));
163 0 : err_cnt++;
164 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
165 0 : seq += FD_VINYL_BSTREAM_BLOCK_SZ;
166 0 : break;
167 0 : }
168 0 : ulong seq1 = seq + block_sz;
169 0 : ulong dev_off1 = seq1 % dev_sz;
170 :
171 0 : if( FD_UNLIKELY( val_esz>sizeof(fd_account_meta_t)+FD_RUNTIME_ACC_SZ_MAX ) ) {
172 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream pair has invalid record size (%lu) at seq=%lu dev_off=%lu", val_esz, seq, dev_off ));
173 0 : err_cnt++;
174 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
175 0 : goto next;
176 0 : }
177 0 : if( FD_UNLIKELY( mm_off>=dev_off1 ) ) {
178 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream pair is fragmented around bstream boundary: seq=%lu dev_off=%lu", seq, dev_off ));
179 0 : err_cnt++;
180 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
181 0 : goto next;
182 0 : }
183 :
184 0 : char const * errstr = fd_vinyl_bstream_pair_test( io_seed, seq, (void *)( mmio+mm_off ), block_sz );
185 0 : if( FD_UNLIKELY( errstr ) ) {
186 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: invalid pair block (%s): key=" VINYL_KEY_FMT " seq=%lu dev_off=%lu", errstr, VINYL_KEY_FMT_ARGS( block.phdr.key ), seq, dev_off ));
187 0 : err_cnt++;
188 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_CORRUPT );
189 0 : goto next;
190 0 : }
191 :
192 0 : ulong memo = fd_vinyl_key_memo( meta_seed, &block.phdr.key );
193 0 : fd_vinyl_meta_ele_t * ele = meta_query_fast( meta, &block.phdr.key, memo );
194 0 : if( FD_UNLIKELY( !ele ) ) {
195 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream pair has no matching meta entry: key=" VINYL_KEY_FMT " memo=%016lx seq=%lu dev_off=%lu",
196 0 : VINYL_KEY_FMT_ARGS( block.phdr.key ), memo, seq, dev_off ));
197 0 : err_cnt++;
198 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
199 0 : goto next;
200 0 : }
201 0 : if( FD_UNLIKELY( ele->seq < seq ) ) {
202 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: meta entry points to older bstream seq: key=" VINYL_KEY_FMT " memo=%016lx meta_seq=%lu bstream_seq=%lu dev_off=%lu",
203 0 : VINYL_KEY_FMT_ARGS( block.phdr.key ), memo, ele->seq, seq, dev_off ));
204 0 : err_cnt++;
205 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
206 0 : goto next;
207 0 : }
208 0 : if( FD_UNLIKELY( ele->seq > seq ) ) goto next; /* ignore, assume bstream entry is stale */
209 :
210 : /* Mark as visited */
211 0 : if( FD_UNLIKELY( ele->line_idx==0UL ) ) {
212 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: duplicate bstream entry detected: key=" VINYL_KEY_FMT " memo=%016lx seq=%lu dev_off=%lu",
213 0 : VINYL_KEY_FMT_ARGS( block.phdr.key ), memo, seq, dev_off ));
214 0 : err_cnt++;
215 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_CORRUPT );
216 0 : goto next;
217 0 : }
218 0 : ele->line_idx = 0UL;
219 :
220 0 : int phdr_ok = fd_memeq( &ele->phdr, &block.phdr, sizeof(fd_vinyl_bstream_phdr_t) );
221 0 : if( FD_UNLIKELY( !phdr_ok ) ) {
222 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream pair header mismatch at seq=%lu dev_off=%lu", seq, dev_off ));
223 0 : err_cnt++;
224 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_CORRUPT );
225 0 : }
226 :
227 : /* At this point, found the latest revision of an account for the
228 : first time */
229 0 : fd_account_meta_t const * meta = fd_type_pun_const( mmio+mm_off+sizeof(fd_vinyl_bstream_phdr_t) );
230 0 : void const * data = (void const *)( meta+1 );
231 0 : void const * pubkey = &ele->phdr.key.uc;
232 0 : ulong data_sz = meta->dlen;
233 0 : ulong lamports = meta->lamports;
234 0 : _Bool executable = !!meta->executable;
235 0 : void const * owner = meta->owner;
236 0 : if( lthash && FD_LIKELY( lamports ) ) {
237 0 : fd_lthash_adder_push_solana_account( adder, sum, pubkey, data, data_sz, lamports, executable, owner );
238 0 : }
239 :
240 0 : next:
241 0 : seq = seq1;
242 0 : break;
243 0 : }
244 :
245 0 : case FD_VINYL_BSTREAM_CTL_TYPE_ZPAD: {
246 0 : char const * errstr = fd_vinyl_bstream_zpad_test( io_seed, seq, &block );
247 0 : if( FD_UNLIKELY( errstr ) ) {
248 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: invalid zpad block (%s): seq=%lu dev_off=%lu", errstr, seq, dev_off ) );
249 0 : err_cnt++;
250 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_INVARIANT );
251 0 : }
252 0 : seq += FD_VINYL_BSTREAM_BLOCK_SZ;
253 0 : break;
254 0 : }
255 :
256 0 : default:
257 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: unexpected block type %i at seq=%lu dev_off=%lu", ctl_type, seq, dev_off ));
258 0 : err_cnt++;
259 0 : return FD_ACCDB_FSCK_INVARIANT;
260 :
261 0 : }
262 :
263 0 : if( FD_UNLIKELY( err_cnt>=err_max ) ) {
264 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: too many errors, stopping" ));
265 0 : return FD_ACCDB_FSCK_UNKNOWN;
266 0 : }
267 0 : }
268 :
269 0 : if( FD_UNLIKELY( seq!=seq_present ) ) {
270 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: bstream scan ended abruptly at seq=%lu (expected %lu)", seq, seq_present ));
271 0 : return FD_ACCDB_FSCK_CORRUPT;
272 0 : }
273 :
274 0 : if( lthash ) {
275 0 : fd_lthash_adder_flush( adder, sum );
276 0 : uchar hash32[32]; fd_blake3_hash( sum->bytes, FD_LTHASH_LEN_BYTES, hash32 );
277 0 : FD_BASE58_ENCODE_32_BYTES( sum->bytes, sum_enc );
278 0 : FD_BASE58_ENCODE_32_BYTES( hash32, hash32_enc );
279 0 : FD_LOG_NOTICE(( "FSCK: lthash[..32]=%s blake3(lthash)=%s", sum_enc, hash32_enc ));
280 0 : }
281 :
282 0 : if( !err_cnt ) FD_LOG_INFO(( "FSCK: bstream OK" ));
283 :
284 : /* Phase 3: Scan meta map left-to-right. Verify that all elements
285 : were visited. */
286 :
287 0 : for( ulong i=0UL; i<ele_max; i++ ) {
288 0 : fd_vinyl_meta_ele_t * ele = &ele0[ i ];
289 0 : if( FD_LIKELY( fd_vinyl_meta_private_ele_is_free( meta->ctx, ele ) ) ) continue;
290 0 : if( FD_UNLIKELY( ele->line_idx==ULONG_MAX ) ) {
291 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: unvisited meta entry detected"
292 0 : " key=%016lx:%016lx:%016lx:%016lx"
293 0 : " memo=%016lx"
294 0 : " meta_idx=%lu"
295 0 : " seq=%lu",
296 0 : fd_ulong_bswap( ele->phdr.key.ul[0] ), fd_ulong_bswap( ele->phdr.key.ul[1] ),
297 0 : fd_ulong_bswap( ele->phdr.key.ul[2] ), fd_ulong_bswap( ele->phdr.key.ul[3] ),
298 0 : ele->memo, i, ele->seq ));
299 0 : if( FD_UNLIKELY( ++err_cnt>=err_max ) ) {
300 0 : FD_LOG_WARNING(( "fd_accdb_fsck_vinyl: too many errors, stopping" ));
301 0 : return FD_ACCDB_FSCK_UNKNOWN;
302 0 : }
303 0 : err = fd_uint_max( err, FD_ACCDB_FSCK_CORRUPT );
304 0 : } else {
305 0 : ele->line_idx = ULONG_MAX; /* reset mark */
306 0 : }
307 0 : }
308 :
309 0 : if( !err_cnt ) FD_LOG_INFO(( "FSCK: meta-bstream sync OK" ));
310 :
311 0 : return err;
312 0 : }
|