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