Line data Source code
1 : #include "fd_solfuzz_private.h"
2 : #include "generated/shred.pb.h"
3 :
4 : #include "../../../ballet/shred/fd_shred.h"
5 : #include "../../../disco/shred/fd_fec_resolver.h"
6 : #include "../../../disco/metrics/fd_metrics.h"
7 : #include "../../../discof/reasm/fd_reasm.h"
8 : #include "../../../discof/replay/fd_sched.h"
9 :
10 : /* Resolver sizing. Production sizing lives in the shred tile setup;
11 : the values below are much smaller because the harness consumes
12 : results synchronously, so partial/complete queue depth has no
13 : consumer-lag to absorb. See fd_fec_resolver.h for the precise
14 : semantics of each depth parameter:
15 : - DEPTH: max in-flight FEC sets before spill
16 : - PARTIAL_DEPTH: out_shred pointer-lifetime guarantee
17 : - COMPLETE_DEPTH: out_fec_set pointer-lifetime guarantee
18 : - DONE_DEPTH: memory of completed (slot, fec_set_idx)
19 : pairs used for duplicate detection */
20 : static ulong const RESOLVER_DEPTH = 32UL;
21 : static ulong const RESOLVER_PARTIAL_DEPTH = 8UL;
22 : static ulong const RESOLVER_COMPLETE_DEPTH = 8UL;
23 : static ulong const RESOLVER_DONE_DEPTH = 256UL;
24 :
25 : /* Reasm + scheduler sizing. Only loosely tied to the resolver depths
26 : above: large enough to cover any reasonable fuzz input while keeping
27 : spad footprint bounded. */
28 : static ulong const REASM_POOL_MAX = 1024UL;
29 :
30 : /* Scheduler txn/block pool sizes, bounded to the fuzz input so
31 : fd_sched_new's per-run free-list init stays cheap. */
32 : static ulong const SCHED_DEPTH = 4096UL;
33 : static ulong const SCHED_BLOCK_CNT_MAX = 128UL;
34 : static ulong const SCHED_EXEC_CNT = 4UL;
35 :
36 : /* Tick-verification parameters passed to fd_sched_block_verify_ticks.
37 : ticks_per_slot=64 (tick_height 0, max_tick_height 64) and
38 : hashes_per_tick=62500 to match mainnet and solfuzz-agave's reference
39 : bank. */
40 : static ulong const SLOT_MAX_TICK_HEIGHT = 64UL;
41 : static ulong const SLOT_HASHES_PER_TICK = 62500UL;
42 :
43 : typedef struct {
44 : fd_hash_t mr;
45 : fd_hash_t cmr;
46 : ulong slot;
47 : uint fec_set_idx;
48 : ushort parent_off;
49 : ushort num_data_shreds;
50 : ushort num_coding_shreds;
51 : uint shred_cnt;
52 : uint shred_offs[ FD_FEC_SHRED_CNT ];
53 : int data_complete;
54 : int slot_complete;
55 : ulong payload_sz;
56 : uchar * payload;
57 : } fd_shred_completed_fec_t;
58 :
59 : #define SORT_NAME sort_completed_fec
60 0 : #define SORT_KEY_T fd_shred_completed_fec_t
61 0 : #define SORT_BEFORE(a,b) ( (a).slot<(b).slot || ( (a).slot==(b).slot && (a).fec_set_idx<(b).fec_set_idx ) )
62 : #define SORT_QUICK_SWAP_MINIMIZE 1 /* ~240B key; skip no-op swaps */
63 : #include "../../../util/tmpl/fd_sort.c"
64 :
65 : /* Return reasm's evicted single-child chain to the pool. */
66 : static void
67 : release_evicted_chain( fd_reasm_t * reasm,
68 0 : fd_reasm_fec_t * evicted ) {
69 0 : while( evicted ) {
70 0 : fd_reasm_fec_t * next = fd_reasm_child( reasm, evicted );
71 0 : fd_reasm_pool_release( reasm, evicted );
72 0 : evicted = next;
73 0 : }
74 0 : }
75 :
76 : /* This is a harness simplification that avoids having to use store.
77 : Also includes logic from the shred tile to concatenate FEC set
78 : payloads into a contiguous buffer. */
79 : static void
80 : capture_completed_fec( fd_spad_t * spad,
81 : fd_fec_set_t const * set,
82 0 : fd_shred_completed_fec_t * out ) {
83 0 : fd_memset( out, 0, sizeof(fd_shred_completed_fec_t) );
84 :
85 0 : fd_shred_t const * base_data = fd_shred_parse( set->data_shreds [ 0 ].b, FD_SHRED_MIN_SZ, FD_SHRED_BLK_MAX );
86 0 : fd_shred_t const * base_parity = fd_shred_parse( set->parity_shreds[ 0 ].b, FD_SHRED_MAX_SZ, FD_SHRED_BLK_MAX );
87 0 : FD_TEST( base_data && base_parity );
88 :
89 0 : ushort data_cnt = base_parity->code.data_cnt;
90 0 : ushort code_cnt = base_parity->code.code_cnt;
91 :
92 : /* Concatenate data-shred payloads into a single buffer. */
93 0 : uchar * payload = fd_spad_alloc( spad, alignof(uchar), (ulong)data_cnt*FD_SHRED_MAX_SZ );
94 0 : ulong payload_sz = 0UL;
95 0 : for( ushort i=0U; i<data_cnt; i++ ) {
96 0 : fd_shred_t const * shred = fd_shred_parse( set->data_shreds[ i ].b, FD_SHRED_MIN_SZ, FD_SHRED_BLK_MAX );
97 0 : FD_TEST( shred );
98 0 : ulong shred_payload_sz = fd_shred_payload_sz( shred );
99 0 : if( FD_LIKELY( shred_payload_sz ) ) {
100 0 : memcpy( payload + payload_sz, fd_shred_data_payload( shred ), shred_payload_sz );
101 0 : payload_sz += shred_payload_sz;
102 0 : }
103 0 : out->shred_offs[ i ] = (uint)payload_sz;
104 0 : }
105 :
106 0 : fd_shred_t const * last = fd_shred_parse( set->data_shreds[ data_cnt-1U ].b, FD_SHRED_MIN_SZ, FD_SHRED_BLK_MAX );
107 0 : FD_TEST( last );
108 :
109 : /* Derive the FEC set's merkle root from a shred's inclusion proof. */
110 0 : uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
111 0 : fd_bmtree_node_t root[1];
112 0 : FD_TEST( fd_shred_merkle_root( base_data, bmtree_mem, root ) );
113 0 : memcpy( out->mr.hash, root->hash, sizeof(out->mr.hash) );
114 0 : out->slot = base_data->slot;
115 0 : out->fec_set_idx = base_data->fec_set_idx;
116 0 : out->parent_off = base_data->data.parent_off;
117 0 : out->num_data_shreds = data_cnt;
118 0 : out->num_coding_shreds = code_cnt;
119 0 : out->shred_cnt = data_cnt;
120 0 : out->data_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE);
121 0 : out->slot_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE);
122 0 : out->payload_sz = payload_sz;
123 0 : out->payload = payload;
124 :
125 : /* Shreds are always chained, so the chained root is present. */
126 0 : memcpy( out->cmr.hash, (uchar const *)base_data + fd_shred_chain_off( base_data->variant ), FD_SHRED_MERKLE_ROOT_SZ );
127 0 : }
128 :
129 : ulong
130 : fd_solfuzz_pb_shred_run( fd_solfuzz_runner_t * runner,
131 : void const * input_,
132 : void ** output_,
133 : void * output_buf,
134 0 : ulong output_bufsz ) {
135 0 : fd_exec_test_shred_parse_context_t const * input = fd_type_pun_const( input_ );
136 0 : fd_exec_test_shred_parse_effects_t ** output = fd_type_pun( output_ );
137 :
138 : /* Initialize output protobuf in the caller-provided buffer. The
139 : fixed-size header allocations (effects struct, per-shred result
140 : array, per-FEC result array) must always fit; the caller is
141 : responsible for sizing output_buf, and a fit-check failure here
142 : is a harness wiring bug. Variable-size payload writes inside the
143 : pop loop are checked separately and treated as soft failures. */
144 0 : ulong output_end = (ulong)output_buf + output_bufsz;
145 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
146 :
147 0 : fd_exec_test_shred_parse_effects_t * effects =
148 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_shred_parse_effects_t), sizeof(fd_exec_test_shred_parse_effects_t) );
149 0 : FD_TEST( _l <= output_end );
150 0 : fd_memset( effects, 0, sizeof(*effects) );
151 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_ACCEPTED;
152 :
153 0 : ulong shred_cnt = (ulong)input->shreds_count;
154 0 : effects->shred_results_count = (pb_size_t)shred_cnt;
155 0 : effects->shred_results =
156 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(bool), sizeof(bool)*shred_cnt );
157 0 : FD_TEST( _l <= output_end );
158 0 : fd_memset( effects->shred_results, 0, sizeof(bool)*shred_cnt );
159 :
160 0 : ulong max_fec_results = shred_cnt;
161 0 : effects->fec_set_results =
162 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_fec_set_parse_result_t), sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results );
163 0 : FD_TEST( _l <= output_end );
164 0 : fd_memset( effects->fec_set_results, 0, sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results );
165 0 : effects->fec_set_results_count = 0U;
166 :
167 : /* The resolver/sched bump FD_MCNT counters (e.g. reject paths) via a
168 : per-thread pointer that is NULL outside a tile. Register a dummy
169 : write-sink to avoid a NULL deref; the values are never read. */
170 0 : static FD_TL uchar harness_metrics[ FD_METRICS_FOOTPRINT( 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN)));
171 0 : fd_metrics_register( (ulong *)fd_metrics_new( harness_metrics, 0UL ) );
172 :
173 : /* Build a resolver configured for the fixture's shred version/root
174 : context. */
175 0 : ulong const resolver_set_cnt = RESOLVER_DEPTH + RESOLVER_PARTIAL_DEPTH + RESOLVER_COMPLETE_DEPTH;
176 :
177 0 : fd_fec_set_t * resolver_sets =
178 0 : fd_spad_alloc( runner->spad, alignof(fd_fec_set_t), sizeof(fd_fec_set_t)*resolver_set_cnt );
179 :
180 0 : ulong resolver_footprint = fd_fec_resolver_footprint( RESOLVER_DEPTH,
181 0 : RESOLVER_PARTIAL_DEPTH,
182 0 : RESOLVER_COMPLETE_DEPTH,
183 0 : RESOLVER_DONE_DEPTH );
184 0 : void * resolver_mem = fd_spad_alloc( runner->spad, fd_fec_resolver_align(), resolver_footprint );
185 :
186 : /* Resolver/reasm/sched construction parameters are all compile-time
187 : constants or sourced from the trusted runner spad -- a NULL return
188 : from any of these constructors is a harness setup bug. */
189 0 : fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new(
190 0 : resolver_mem,
191 0 : NULL, NULL,
192 0 : RESOLVER_DEPTH,
193 0 : RESOLVER_PARTIAL_DEPTH,
194 0 : RESOLVER_COMPLETE_DEPTH,
195 0 : RESOLVER_DONE_DEPTH,
196 0 : resolver_sets,
197 0 : 0UL ) );
198 0 : FD_TEST( resolver );
199 :
200 : /* Configure resolver behavior for the fuzz run:
201 : - bypass Merkle proof + Ed25519 signature checks
202 : (see fd_fec_resolver_set_bypass_verify in fd_fec_resolver.h)
203 : - drop shreds for slots strictly older than root_slot
204 : - map the proto bool for the discard-unexpected-DATA_COMPLETE
205 : feature onto the activation_slot extremes (0 = active for
206 : every slot, ULONG_MAX = never active) */
207 0 : fd_fec_resolver_set_shred_version( resolver, (ushort)input->shred_version );
208 0 : fd_fec_resolver_set_bypass_verify( resolver, 1 );
209 0 : fd_fec_resolver_advance_slot_old ( resolver, input->root_slot );
210 0 : fd_fec_resolver_set_discard_unexpected_data_complete_shreds( resolver,
211 0 : input->features.discard_unexpected_data_complete_shreds ? 0UL : ULONG_MAX );
212 :
213 : /* Initialize reasm/sched so completed FEC sets can be
214 : replay-ingested in order. */
215 0 : void * reasm_mem = fd_spad_alloc( runner->spad, fd_reasm_align(), fd_reasm_footprint( REASM_POOL_MAX ) );
216 0 : fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( reasm_mem, REASM_POOL_MAX, 0UL ) );
217 0 : FD_TEST( reasm );
218 0 : int reasm_initialized = 0;
219 :
220 0 : fd_rng_t rng_mem[1];
221 0 : fd_rng_t * rng = fd_rng_join( fd_rng_new( rng_mem, 0U, 0UL ) );
222 0 : void * sched_mem = fd_spad_alloc( runner->spad, fd_sched_align(), fd_sched_footprint( SCHED_DEPTH, SCHED_BLOCK_CNT_MAX ) );
223 0 : fd_sched_t * sched = fd_sched_join( fd_sched_new( sched_mem, rng, SCHED_DEPTH, SCHED_BLOCK_CNT_MAX, SCHED_EXEC_CNT ) );
224 0 : FD_TEST( sched );
225 0 : fd_sched_set_bypass_poh_verify( sched, 1 ); /* skip PoH end_hash compare */
226 0 : fd_sched_set_bypass_alut_resolution( sched, 1 ); /* skip ALUT resolution (no accdb) */
227 0 : fd_sched_block_add_done( sched, 0UL, ULONG_MAX, input->root_slot );
228 0 : fd_sched_root_notify( sched, 0UL );
229 0 : fd_sched_advance_root( sched, 0UL );
230 :
231 : /* completed[] caches FEC payloads for sched ingestion. This is a
232 : simplified version of store specifically for the harness. Instead
233 : of querying store for FEC set data, we just store it in this
234 : local array. */
235 0 : fd_shred_completed_fec_t * completed =
236 0 : fd_spad_alloc( runner->spad, alignof(fd_shred_completed_fec_t), sizeof(fd_shred_completed_fec_t)*max_fec_results );
237 0 : ulong completed_cnt = 0UL;
238 0 : ulong next_bank_idx = 1UL; /* 0 is the reasm/sched root, so start at 1 */
239 :
240 : /* Parse each shred, feed resolver, and only continue when an FEC
241 : completes. */
242 0 : fd_pubkey_t dummy_leader_pubkey = {0};
243 0 : for( ulong i=0UL; i<shred_cnt; i++ ) {
244 0 : pb_bytes_array_t const * shred_msg = input->shreds[ i ];
245 0 : FD_TEST( shred_msg );
246 0 : FD_TEST( shred_msg->size>=FD_SHRED_MIN_SZ && shred_msg->size<=FD_SHRED_MAX_SZ );
247 :
248 : /* Step 1: fd_shred_parse() */
249 0 : fd_shred_t const * shred = fd_shred_parse( shred_msg->bytes, shred_msg->size, FD_SHRED_BLK_MAX );
250 :
251 : /* Unchained merkle shreds are dropped in the shred tile. */
252 0 : if( !shred || !fd_shred_is_chained( fd_shred_type( shred->variant ) ) ) {
253 0 : effects->shred_results[ i ] = false;
254 0 : continue;
255 0 : }
256 :
257 : /* Parse-level accept, matching Agave; the resolver verdict below is not folded in. */
258 0 : effects->shred_results[ i ] = true;
259 :
260 : /* Step 2: fd_fec_resolver_add_shred() */
261 0 : fd_fec_set_t const * out_fec_set = NULL;
262 0 : fd_shred_t const * out_shred = NULL;
263 0 : fd_bmtree_node_t out_merkle_root[1];
264 0 : int rc = fd_fec_resolver_add_shred(
265 0 : resolver,
266 0 : shred,
267 0 : shred_msg->size,
268 0 : FD_SHRED_BLK_MAX,
269 0 : 0,
270 0 : dummy_leader_pubkey.uc,
271 0 : &out_fec_set,
272 0 : &out_shred,
273 0 : out_merkle_root,
274 0 : NULL
275 0 : );
276 0 : (void)out_shred;
277 :
278 : /* We only want to replay the FEC set once it's complete. */
279 0 : if( rc!=FD_FEC_RESOLVER_SHRED_COMPLETES ) continue;
280 :
281 : /* completed[] is sized to shred_cnt, which is a strict upper bound
282 : on completion count, so overflow here is a harness sizing bug. */
283 0 : FD_TEST( completed_cnt<max_fec_results );
284 :
285 : /* Construct the completed FEC set record. The merkle root is derived
286 : from the shred's inclusion proof inside capture_completed_fec. */
287 0 : fd_shred_completed_fec_t * rec = &completed[ completed_cnt ];
288 0 : capture_completed_fec( runner->spad, out_fec_set, rec );
289 :
290 : /* Buffer the completion; reasm runs after the shred loop in
291 : (slot, fec_set_idx) order. */
292 0 : completed_cnt++;
293 0 : }
294 :
295 : /* Sort by (slot, fec_set_idx) so set 0 anchors the reasm root
296 : before any successor inserts. */
297 0 : sort_completed_fec_inplace( completed, completed_cnt );
298 :
299 0 : for( ulong k=0UL; k<completed_cnt; k++ ) {
300 0 : fd_shred_completed_fec_t * rec = &completed[ k ];
301 :
302 : /* Lazily init reasm off the first (lowest slot/fec_set_idx) FEC to set the root block id. */
303 0 : if( FD_UNLIKELY( !reasm_initialized ) ) {
304 0 : fd_reasm_fec_t * root = fd_reasm_init( reasm, &rec->cmr, input->root_slot );
305 0 : if( FD_UNLIKELY( !root ) ) {
306 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
307 0 : continue;
308 0 : }
309 0 : root->bank_idx = 0UL;
310 0 : reasm_initialized = 1;
311 0 : }
312 :
313 : /* Step 3: fd_reasm_insert()
314 : Insert completed FEC into reasm and release elements from evicted
315 : chains. */
316 0 : fd_reasm_fec_t * evicted = NULL;
317 0 : if( !fd_reasm_insert( reasm,
318 0 : &rec->mr,
319 0 : &rec->cmr,
320 0 : rec->slot,
321 0 : rec->fec_set_idx,
322 0 : rec->parent_off,
323 0 : rec->num_data_shreds,
324 0 : rec->data_complete,
325 0 : rec->slot_complete,
326 0 : 0,
327 0 : NULL,
328 0 : &evicted ) ) {
329 0 : release_evicted_chain( reasm, evicted );
330 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
331 0 : continue;
332 0 : }
333 0 : release_evicted_chain( reasm, evicted );
334 :
335 : /* Step 4: fd_reasm_pop() and fd_fec_set_ingest() while there are
336 : completed FEC sets remaining. */
337 0 : fd_reasm_fec_t * popped;
338 0 : while( FD_LIKELY( (popped = fd_reasm_pop( reasm )) ) ) {
339 : /* Query our "store"-adjacent structure (completed) for the FEC
340 : set payload */
341 0 : fd_shred_completed_fec_t * popped_rec = NULL;
342 0 : for( ulong j=0UL; j<completed_cnt; j++ ) {
343 0 : if( FD_LIKELY( !memcmp( completed[ j ].mr.hash, popped->key.hash, sizeof(fd_hash_t) ) ) ) {
344 0 : popped_rec = &completed[ j ];
345 0 : break;
346 0 : }
347 0 : }
348 0 : FD_TEST( popped_rec );
349 0 : FD_TEST( effects->fec_set_results_count<max_fec_results );
350 :
351 : /* Capture completed FEC set results */
352 0 : fd_exec_test_fec_set_parse_result_t * out_fec = &effects->fec_set_results[ effects->fec_set_results_count++ ];
353 0 : fd_memset( out_fec, 0, sizeof(*out_fec) );
354 0 : out_fec->completed = true;
355 0 : out_fec->slot = popped_rec->slot;
356 0 : out_fec->fec_set_index = popped_rec->fec_set_idx;
357 0 : out_fec->parent_offset = popped_rec->parent_off;
358 0 : out_fec->shred_version = input->shred_version;
359 0 : out_fec->num_data_shreds = popped_rec->num_data_shreds;
360 0 : out_fec->num_coding_shreds = popped_rec->num_coding_shreds;
361 0 : memcpy( out_fec->merkle_root, popped_rec->mr.hash, FD_SHRED_MERKLE_ROOT_SZ );
362 0 : memcpy( out_fec->chained_merkle_root, popped_rec->cmr.hash, FD_SHRED_MERKLE_ROOT_SZ );
363 0 : if( FD_LIKELY( popped_rec->payload_sz ) ) {
364 0 : out_fec->payload = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( popped_rec->payload_sz ) );
365 0 : if( FD_UNLIKELY( _l > output_end ) ) {
366 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
367 0 : effects->fec_set_results_count--;
368 0 : break;
369 0 : }
370 0 : out_fec->payload->size = (pb_size_t)popped_rec->payload_sz;
371 0 : memcpy( out_fec->payload->bytes, popped_rec->payload, popped_rec->payload_sz );
372 0 : }
373 :
374 : /* Match Agave's model of only parsing deshreddable batches.
375 : Otherwise, Firedancer's eager per-FEC parsing might reject a
376 : block for a bad FEC set that Agave simply doesn't parse at all.
377 : A FEC set is deshreddable iff some FEC set at or after it in
378 : the same slot carries DATA_COMPLETE. Complete batches are
379 : still fed one FEC set at a time. Only the final, potentially
380 : incomplete batch is held back. fec_set_results were already
381 : captured above, independent of the scheduler, so withholding
382 : does not change them. Agave likewise emits a fec_set_result
383 : for every complete FEC set, deshreddable or not. */
384 0 : int deshreddable = 0;
385 0 : for( ulong j=0UL; j<completed_cnt; j++ ) {
386 0 : if( completed[ j ].slot==popped_rec->slot && completed[ j ].fec_set_idx>=popped_rec->fec_set_idx && completed[ j ].data_complete ) {
387 0 : deshreddable = 1;
388 0 : break;
389 0 : }
390 0 : }
391 0 : if( !deshreddable ) continue;
392 :
393 : /* Bank lineage comes from the reasm tree, like the replay tile.
394 : Reasm pops parents before children, so the parent's bank_idx
395 : was already recorded by the time we read it here. */
396 0 : fd_reasm_fec_t * parent = fd_reasm_parent( reasm, popped );
397 0 : FD_TEST( parent );
398 0 : ulong parent_bank_idx = parent->bank_idx;
399 :
400 : /* If the FEC set starts a new slot (no bank yet), use a fresh
401 : bank index; otherwise reuse the parent's. Record it on the
402 : node so this slot's later FECs inherit it. */
403 0 : ulong bank_idx = ( popped->fec_set_idx==0U ) ? next_bank_idx++ : parent_bank_idx;
404 0 : popped->bank_idx = bank_idx;
405 :
406 0 : fd_store_fec_t store_fec[1] = {0};
407 0 : store_fec->key.merkle_root = popped_rec->mr;
408 0 : store_fec->data_sz = popped_rec->payload_sz;
409 0 : memcpy( store_fec->shred_offs, popped_rec->shred_offs, sizeof(store_fec->shred_offs) );
410 :
411 0 : fd_sched_fec_t sched_fec = {
412 0 : .bank_idx = bank_idx,
413 0 : .parent_bank_idx = parent_bank_idx,
414 0 : .slot = popped_rec->slot,
415 0 : .parent_slot = fd_ulong_if( popped_rec->slot>=popped_rec->parent_off, popped_rec->slot-popped_rec->parent_off, 0UL ),
416 0 : .fec = store_fec,
417 0 : .data = popped_rec->payload,
418 0 : .shred_cnt = popped_rec->shred_cnt,
419 0 : .is_last_in_batch = !!popped_rec->data_complete,
420 0 : .is_last_in_block = !!popped_rec->slot_complete,
421 0 : .is_first_in_block = !!(popped_rec->fec_set_idx==0U)
422 0 : };
423 :
424 : /* Drain abandoned blocks; fd_sched_fec_ingest requires an empty
425 : ref_q. The replay tile drains the same queue (to decrement bank
426 : refcounts); the harness has no banks, so it just discards. */
427 0 : while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {}
428 :
429 : /* Final step: ingest the completed FEC set. */
430 0 : FD_TEST( fd_sched_fec_can_ingest( sched, &sched_fec ) );
431 0 : if( !fd_sched_fec_ingest( sched, &sched_fec ) ) {
432 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
433 0 : } else if( FD_LIKELY( bank_idx!=0UL ) ) {
434 : /* Harness stops at FEC ingest, so verify this slot's tick
435 : window here; skip bank_idx 0 (reasm/sched root). Only
436 : complete batches reach this point, so this matches Agave's
437 : model of only verifying deshredded batches. */
438 0 : if( fd_sched_block_verify_ticks( sched, bank_idx, 0UL, SLOT_MAX_TICK_HEIGHT, SLOT_HASHES_PER_TICK ) ) {
439 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
440 0 : }
441 0 : }
442 0 : }
443 0 : }
444 :
445 : /* Finalize scratch allocation and return encoded effects span. */
446 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
447 0 : *output = effects;
448 0 : return actual_end - (ulong)output_buf;
449 0 : }
450 :
|