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