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 "../../../disco/repair/fd_repair.h"
8 : #include "../../../discof/replay/fd_sched.h"
9 : #include "../../alpenglow/fd_block_marker_serde.h"
10 :
11 : /* Resolver sizing. Production sizing lives in the shred tile setup;
12 : the values below are much smaller because the harness consumes
13 : results synchronously, so partial/complete queue depth has no
14 : consumer-lag to absorb. See fd_fec_resolver.h for the precise
15 : semantics of each depth parameter:
16 : - DEPTH: max in-flight FEC sets before spill
17 : - PARTIAL_DEPTH: out_shred pointer-lifetime guarantee
18 : - COMPLETE_DEPTH: out_fec_set pointer-lifetime guarantee
19 : - DONE_DEPTH: memory of completed (slot, fec_set_idx)
20 : pairs used for duplicate detection
21 : - SLOT_MAX: max slots tracked by the resolver */
22 : static ulong const RESOLVER_DEPTH = 32UL;
23 : static ulong const RESOLVER_PARTIAL_DEPTH = 8UL;
24 : static ulong const RESOLVER_COMPLETE_DEPTH = 8UL;
25 : static ulong const RESOLVER_DONE_DEPTH = 256UL;
26 : static ulong const RESOLVER_SLOT_MAX = 4096UL;
27 :
28 : /* Scheduler txn/block pool sizes, bounded to the fuzz input so
29 : fd_sched_new's per-run free-list init stays cheap. */
30 : static ulong const SCHED_DEPTH = 4096UL;
31 : static ulong const SCHED_BLOCK_CNT_MAX = 128UL;
32 : static ulong const SCHED_EXEC_CNT = 4UL;
33 :
34 : /* Tick window passed to fd_sched_block_verify_ticks. Alpenglow blocks
35 : carry one tick, so a slot starts at tick_height max_tick_height-1
36 : (ticks_per_slot=64 to match solfuzz-agave's reference bank). */
37 : static ulong const SLOT_MAX_TICK_HEIGHT = 64UL;
38 :
39 : typedef struct {
40 : fd_hash_t mr;
41 : fd_hash_t cmr;
42 : ulong slot;
43 : uint fec_set_idx;
44 : ushort parent_off;
45 : ushort num_data_shreds;
46 : ushort num_coding_shreds;
47 : uint shred_cnt;
48 : ushort shred_sz[ FD_FEC_SHRED_CNT ];
49 : int data_complete;
50 : int slot_complete;
51 : int chained; /* in its slot's contiguous chain from FEC set 0 */
52 : ulong payload_sz;
53 : uchar * payload;
54 : uchar * data_shreds; /* num_data_shreds copies of FD_SHRED_MIN_SZ bytes */
55 : } fd_shred_completed_fec_t;
56 :
57 : #define SORT_NAME sort_completed_fec
58 7128 : #define SORT_KEY_T fd_shred_completed_fec_t
59 3564 : #define SORT_BEFORE(a,b) ( (a).slot<(b).slot || ( (a).slot==(b).slot && (a).fec_set_idx<(b).fec_set_idx ) )
60 : #define SORT_QUICK_SWAP_MINIMIZE 1 /* ~240B key; skip no-op swaps */
61 : #include "../../../util/tmpl/fd_sort.c"
62 :
63 : /* This is a harness simplification that avoids having to use store.
64 : Also includes logic from the shred tile to concatenate FEC set
65 : payloads into a contiguous buffer. */
66 : static void
67 : capture_completed_fec( fd_spad_t * spad,
68 : fd_fec_set_t const * set,
69 4572 : fd_shred_completed_fec_t * out ) {
70 4572 : fd_memset( out, 0, sizeof(fd_shred_completed_fec_t) );
71 :
72 4572 : fd_shred_t const * base_data = fd_shred_parse( set->data_shreds [ 0 ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
73 4572 : fd_shred_t const * base_parity = fd_shred_parse( set->parity_shreds[ 0 ].b, FD_SHRED_MAX_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
74 4572 : FD_TEST( base_data && base_parity );
75 :
76 4572 : ushort data_cnt = base_parity->code.data_cnt;
77 4572 : ushort code_cnt = base_parity->code.code_cnt;
78 :
79 : /* Concatenate data-shred payloads into a single buffer. */
80 4572 : uchar * payload = fd_spad_alloc( spad, alignof(uchar), (ulong)data_cnt*FD_SHRED_MAX_SZ );
81 4572 : uchar * data_shreds = fd_spad_alloc( spad, alignof(fd_shred_t), (ulong)data_cnt*FD_SHRED_MIN_SZ );
82 4572 : ulong payload_sz = 0UL;
83 150661 : for( ushort i=0U; i<data_cnt; i++ ) {
84 146089 : fd_shred_t const * shred = fd_shred_parse( set->data_shreds[ i ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
85 146089 : FD_TEST( shred );
86 146089 : memcpy( data_shreds + (ulong)i*FD_SHRED_MIN_SZ, set->data_shreds[ i ].b, FD_SHRED_MIN_SZ );
87 146089 : ulong shred_payload_sz = fd_shred_payload_sz( shred );
88 146089 : if( FD_LIKELY( shred_payload_sz ) ) {
89 5608 : memcpy( payload + payload_sz, fd_shred_data_payload( shred ), shred_payload_sz );
90 5608 : payload_sz += shred_payload_sz;
91 5608 : }
92 146089 : FD_TEST( shred_payload_sz<=USHORT_MAX );
93 146089 : out->shred_sz[ i ] = (ushort)shred_payload_sz;
94 146089 : }
95 :
96 4572 : fd_shred_t const * last = fd_shred_parse( set->data_shreds[ data_cnt-1U ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
97 4572 : FD_TEST( last );
98 :
99 : /* Derive the FEC set's merkle root from a shred's inclusion proof. */
100 4572 : uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
101 4572 : fd_bmtree_node_t root[1];
102 4572 : FD_TEST( fd_shred_merkle_root( base_data, bmtree_mem, root ) );
103 4572 : memcpy( out->mr.hash, root->hash, sizeof(out->mr.hash) );
104 4572 : out->slot = base_data->slot;
105 4572 : out->fec_set_idx = base_data->fec_set_idx;
106 4572 : out->parent_off = base_data->data.parent_off;
107 4572 : out->num_data_shreds = data_cnt;
108 4572 : out->num_coding_shreds = code_cnt;
109 4572 : out->shred_cnt = data_cnt;
110 4572 : out->data_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE);
111 4572 : out->slot_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE);
112 4572 : out->payload_sz = payload_sz;
113 4572 : out->payload = payload;
114 4572 : out->data_shreds = data_shreds;
115 :
116 : /* Shreds are always chained, so the chained root is present. */
117 4572 : memcpy( out->cmr.hash, (uchar const *)base_data + fd_shred_chain_off( base_data->variant ), FD_SHRED_MERKLE_ROOT_SZ );
118 4572 : }
119 :
120 : ulong
121 : fd_solfuzz_pb_shred_run( fd_solfuzz_runner_t * runner,
122 : void const * input_,
123 : void ** output_,
124 : void * output_buf,
125 1006 : ulong output_bufsz ) {
126 1006 : fd_exec_test_shred_parse_context_t const * input = fd_type_pun_const( input_ );
127 1006 : fd_exec_test_shred_parse_effects_t ** output = fd_type_pun( output_ );
128 :
129 : /* Initialize output protobuf in the caller-provided buffer. The
130 : fixed-size header allocations (effects struct, per-shred result
131 : array, per-FEC result array) must always fit; the caller is
132 : responsible for sizing output_buf, and a fit-check failure here
133 : is a harness wiring bug. Variable-size payload writes inside the
134 : pop loop are checked separately and treated as soft failures. */
135 1006 : ulong output_end = (ulong)output_buf + output_bufsz;
136 1006 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
137 :
138 1006 : fd_exec_test_shred_parse_effects_t * effects =
139 1006 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_shred_parse_effects_t), sizeof(fd_exec_test_shred_parse_effects_t) );
140 1006 : FD_TEST( _l <= output_end );
141 1006 : fd_memset( effects, 0, sizeof(*effects) );
142 1006 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_ACCEPTED;
143 :
144 1006 : ulong shred_cnt = (ulong)input->shreds_count;
145 1006 : effects->shred_results_count = (uint)shred_cnt;
146 1006 : effects->shred_results =
147 1006 : FD_SCRATCH_ALLOC_APPEND( l, alignof(bool), sizeof(bool)*shred_cnt );
148 1006 : FD_TEST( _l <= output_end );
149 1006 : fd_memset( effects->shred_results, 0, sizeof(bool)*shred_cnt );
150 :
151 1006 : ulong max_fec_results = shred_cnt;
152 1006 : effects->fec_set_results =
153 1006 : 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 );
154 1006 : FD_TEST( _l <= output_end );
155 1006 : fd_memset( effects->fec_set_results, 0, sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results );
156 1006 : effects->fec_set_results_count = 0U;
157 :
158 : /* The resolver/sched bump FD_MCNT counters (e.g. reject paths) via a
159 : per-thread pointer that is NULL outside a tile. Register a dummy
160 : write-sink to avoid a NULL deref; the values are never read. */
161 1006 : static FD_TL uchar harness_metrics[ FD_METRICS_FOOTPRINT( 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN)));
162 1006 : fd_metrics_register( (ulong *)fd_metrics_new( harness_metrics, 0UL ) );
163 :
164 : /* Build a resolver configured for the fixture's shred version/root
165 : context. */
166 1006 : ulong const resolver_set_cnt = RESOLVER_DEPTH + RESOLVER_PARTIAL_DEPTH + RESOLVER_COMPLETE_DEPTH;
167 :
168 1006 : fd_fec_set_t * resolver_sets =
169 1006 : fd_spad_alloc( runner->spad, alignof(fd_fec_set_t), sizeof(fd_fec_set_t)*resolver_set_cnt );
170 :
171 1006 : ulong resolver_footprint = fd_fec_resolver_footprint( RESOLVER_DEPTH,
172 1006 : RESOLVER_PARTIAL_DEPTH,
173 1006 : RESOLVER_COMPLETE_DEPTH,
174 1006 : RESOLVER_DONE_DEPTH );
175 1006 : void * resolver_mem = fd_spad_alloc( runner->spad, fd_fec_resolver_align(), resolver_footprint );
176 :
177 : /* Resolver/repair/sched construction parameters are all compile-time
178 : constants or sourced from the trusted runner spad -- a NULL return
179 : from any of these constructors is a harness setup bug. */
180 1006 : fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new(
181 1006 : resolver_mem,
182 1006 : RESOLVER_DEPTH,
183 1006 : RESOLVER_PARTIAL_DEPTH,
184 1006 : RESOLVER_COMPLETE_DEPTH,
185 1006 : RESOLVER_DONE_DEPTH,
186 1006 : RESOLVER_SLOT_MAX,
187 1006 : resolver_sets,
188 1006 : 0UL ) );
189 1006 : FD_TEST( resolver );
190 :
191 : /* Configure resolver behavior for the fuzz run:
192 : - bypass Merkle proof + Ed25519 signature checks
193 : (see fd_fec_resolver_set_bypass_verify in fd_fec_resolver.h)
194 : - drop shreds for slots strictly older than root_slot */
195 1006 : fd_fec_resolver_set_shred_version( resolver, (ushort)input->shred_version );
196 1006 : fd_fec_resolver_set_bypass_verify( resolver, 1 );
197 1006 : fd_fec_resolver_advance_slot_old ( resolver, input->root_slot );
198 :
199 : /* Initialize sched so completed FEC sets can be replay-ingested in
200 : order. Repair is set up below, once the root block id is known. */
201 1006 : fd_rng_t rng_mem[1];
202 1006 : fd_rng_t * rng = fd_rng_join( fd_rng_new( rng_mem, 0U, 0UL ) );
203 1006 : 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 ) );
204 1006 : 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 ) );
205 1006 : FD_TEST( sched );
206 1006 : fd_sched_set_bypass_poh_verify( sched, 1 ); /* skip PoH end_hash compare */
207 1006 : fd_sched_set_bypass_alut_resolution( sched, 1 ); /* skip ALUT resolution (no accdb) */
208 1006 : fd_sched_block_add_done( sched, 0UL, ULONG_MAX, input->root_slot );
209 1006 : fd_sched_root_notify( sched, 0UL );
210 1006 : fd_sched_advance_root( sched, 0UL );
211 :
212 : /* completed[] caches FEC payloads for sched ingestion. This is a
213 : simplified version of store specifically for the harness. Instead
214 : of querying store for FEC set data, we just store it in this
215 : local array. */
216 1006 : fd_shred_completed_fec_t * completed =
217 1006 : fd_spad_alloc( runner->spad, alignof(fd_shred_completed_fec_t), sizeof(fd_shred_completed_fec_t)*max_fec_results );
218 1006 : ulong completed_cnt = 0UL;
219 1006 : uint next_bank_idx = 1U; /* 0 is the repair/sched root, so start at 1 */
220 :
221 : /* Parse each shred, feed resolver, and only continue when an FEC
222 : completes. */
223 1006 : fd_pubkey_t dummy_leader_pubkey = {0};
224 147337 : for( ulong i=0UL; i<shred_cnt; i++ ) {
225 146331 : fd_pb_bytes_array_t const * shred_msg = input->shreds[ i ];
226 146331 : FD_TEST( shred_msg );
227 146331 : FD_TEST( shred_msg->size>=FD_SHRED_MIN_SZ && shred_msg->size<=FD_SHRED_MAX_SZ );
228 :
229 : /* Step 1: fd_shred_parse() */
230 146331 : fd_shred_t const * shred = fd_shred_parse( shred_msg->bytes, shred_msg->size, FD_SHRED_PROTOCOL_BLK_MAX );
231 :
232 : /* Unchained merkle shreds are dropped in the shred tile. */
233 146331 : if( !shred || !fd_shred_is_chained( fd_shred_type( shred->variant ) ) ) {
234 4 : effects->shred_results[ i ] = false;
235 4 : continue;
236 4 : }
237 :
238 : /* Parse-level accept, matching Agave; the resolver verdict below is not folded in. */
239 146327 : effects->shred_results[ i ] = true;
240 :
241 : /* Step 2: fd_fec_resolver_add_shred() */
242 146327 : fd_fec_set_t const * out_fec_set = NULL;
243 146327 : fd_shred_t const * out_shred = NULL;
244 146327 : fd_bmtree_node_t out_merkle_root[1];
245 146327 : int rc = fd_fec_resolver_add_shred(
246 146327 : resolver,
247 146327 : shred,
248 146327 : shred_msg->size,
249 146327 : FD_SHRED_PROTOCOL_BLK_MAX,
250 146327 : FD_FEC_RESOLVER_SHRED_SRC_TURBINE,
251 146327 : dummy_leader_pubkey.uc,
252 146327 : &out_fec_set,
253 146327 : &out_shred,
254 146327 : out_merkle_root,
255 146327 : NULL
256 146327 : );
257 146327 : (void)out_shred;
258 :
259 : /* We only want to replay the FEC set once it's complete. */
260 146327 : if( rc!=FD_FEC_RESOLVER_SHRED_COMPLETES ) continue;
261 :
262 : /* completed[] is sized to shred_cnt, which is a strict upper bound
263 : on completion count, so overflow here is a harness sizing bug. */
264 4572 : FD_TEST( completed_cnt<max_fec_results );
265 :
266 : /* Construct the completed FEC set record. The merkle root is derived
267 : from the shred's inclusion proof inside capture_completed_fec. */
268 4572 : fd_shred_completed_fec_t * rec = &completed[ completed_cnt ];
269 4572 : capture_completed_fec( runner->spad, out_fec_set, rec );
270 :
271 : /* Buffer the completion; repair runs after the shred loop in
272 : (slot, fec_set_idx) order. */
273 4572 : completed_cnt++;
274 4572 : }
275 :
276 : /* Sort by (slot, fec_set_idx) so blocks reach repair in order. */
277 1006 : sort_completed_fec_inplace( completed, completed_cnt );
278 :
279 : /* One result per completed FEC set, as both validators complete sets
280 : whether or not the slot is connected: per slot, the contiguous run
281 : from index 0 in which every set chains to the previous one's
282 : Merkle root. Only these sets are given to repair below, which
283 : drives the block verdict. */
284 5578 : for( ulong k=0UL; k<completed_cnt; k++ ) {
285 4572 : fd_shred_completed_fec_t * rec = &completed[ k ];
286 4572 : int first_in_slot = ( k==0UL ) || ( completed[ k-1UL ].slot!=rec->slot );
287 4572 : if( first_in_slot ) {
288 1008 : rec->chained = rec->fec_set_idx==0U;
289 3564 : } else {
290 3564 : fd_shred_completed_fec_t * prev = &completed[ k-1UL ];
291 3564 : rec->chained = prev->chained && rec->fec_set_idx==prev->fec_set_idx+FD_FEC_SHRED_CNT && !memcmp( rec->cmr.hash, prev->mr.hash, sizeof(fd_hash_t) );
292 3564 : }
293 4572 : if( !rec->chained ) continue;
294 4557 : FD_TEST( effects->fec_set_results_count<max_fec_results );
295 4557 : fd_exec_test_fec_set_parse_result_t * out_fec = &effects->fec_set_results[ effects->fec_set_results_count++ ];
296 4557 : fd_memset( out_fec, 0, sizeof(*out_fec) );
297 4557 : out_fec->completed = true;
298 4557 : out_fec->slot = rec->slot;
299 4557 : out_fec->fec_set_index = rec->fec_set_idx;
300 4557 : out_fec->parent_offset = rec->parent_off;
301 4557 : out_fec->shred_version = input->shred_version;
302 4557 : out_fec->num_data_shreds = rec->num_data_shreds;
303 4557 : out_fec->num_coding_shreds = rec->num_coding_shreds;
304 4557 : memcpy( out_fec->merkle_root, rec->mr.hash, FD_SHRED_MERKLE_ROOT_SZ );
305 4557 : memcpy( out_fec->chained_merkle_root, rec->cmr.hash, FD_SHRED_MERKLE_ROOT_SZ );
306 4557 : out_fec->payload_hash = fd_solfuzz_hash( rec->payload, rec->payload_sz );
307 4557 : }
308 :
309 : /* The root block id is the parent block id named by the block header
310 : in shred 0 of the first completed block that builds on the root
311 : slot. Without one, nothing can link to the root, but repair still
312 : invalidates slots with a bad block header. */
313 1006 : fd_mr32_t root_blk_mr = {0};
314 1020 : for( ulong k=0UL; k<completed_cnt; k++ ) {
315 1015 : fd_shred_completed_fec_t * rec = &completed[ k ];
316 1015 : if( rec->fec_set_idx!=0U || rec->slot<rec->parent_off || rec->slot-rec->parent_off!=input->root_slot ) continue;
317 1001 : fd_block_marker_t marker[1];
318 1001 : if( fd_block_marker_de( marker, rec->payload, rec->shred_sz[ 0 ] ) || marker->kind!=FD_BLOCK_MARKER_KIND_HEADER ) continue;
319 1001 : root_blk_mr = FD_LOAD( fd_mr32_t, marker->header.parent_block_id.uc );
320 1001 : break;
321 1001 : }
322 :
323 : /* Repair window. Shreds for slots outside (root_slot,
324 : root_slot+slot_max) are not given to repair, like the repair tile.
325 : The harness never advances the root, so the window covers every
326 : completed slot (the resolver bounds these by RESOLVER_SLOT_MAX).
327 : It is sized to the input since fd_repair_new clears its whole
328 : footprint on every run. */
329 1006 : ulong slot_max = 1UL;
330 5578 : for( ulong k=0UL; k<completed_cnt; k++ ) {
331 4572 : if( completed[ k ].slot>input->root_slot ) slot_max = fd_ulong_max( slot_max, completed[ k ].slot-input->root_slot+1UL );
332 4572 : }
333 1006 : FD_TEST( slot_max<=RESOLVER_SLOT_MAX );
334 1006 : ulong fec_max = fd_ulong_max( completed_cnt, 1UL );
335 1006 : ulong fec_blk_max = FD_SHRED_PROTOCOL_BLK_MAX/FD_FEC_SHRED_CNT;
336 1006 : void * repair_mem = fd_spad_alloc( runner->spad, fd_repair_align(), fd_repair_footprint( slot_max, fec_max, fec_blk_max ) );
337 1006 : fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, slot_max, fec_max, fec_blk_max, 0UL ) );
338 1006 : FD_TEST( repair );
339 1006 : fd_repair_init( repair, input->root_slot, &root_blk_mr, NULL, NULL );
340 :
341 : /* Bank index of each repair blk, UINT_MAX until its first FEC set
342 : (FEC set 0, or its UpdateParent's) is ingested. Repair never frees a blk here, so a blk pool idx names
343 : one blk for the whole run. */
344 1006 : ulong blk_max = fd_repair_blk_pool_max( repair->blk_pool );
345 1006 : uint * blk_bank_idx = fd_spad_alloc( runner->spad, alignof(uint), sizeof(uint)*blk_max );
346 6081 : for( ulong j=0UL; j<blk_max; j++ ) blk_bank_idx[ j ] = UINT_MAX;
347 1006 : blk_bank_idx[ fd_repair_blk_pool_idx( repair->blk_pool, repair->root_blk ) ] = 0U;
348 :
349 5577 : for( ulong k=0UL; k<completed_cnt; k++ ) {
350 4571 : fd_shred_completed_fec_t * rec = &completed[ k ];
351 :
352 : /* Step 3: fd_repair_shred_insert() and fd_repair_fec_complete()
353 : Give repair the FEC set's data shreds, then its completion, with
354 : the repair tile's window and parent filters. */
355 4571 : if( !rec->chained ) continue;
356 4557 : if( rec->slot<=input->root_slot || rec->slot>=input->root_slot+slot_max ) continue;
357 4556 : if( (ulong)rec->fec_set_idx+rec->num_data_shreds>fec_blk_max*FD_FEC_SHRED_CNT ) continue;
358 4556 : fd_shred_t const * shred = NULL;
359 4556 : int ok = 0;
360 150350 : for( ulong i=0UL; i<rec->num_data_shreds; i++ ) {
361 145794 : shred = (fd_shred_t const *)( rec->data_shreds + i*FD_SHRED_MIN_SZ );
362 145795 : ok = shred->data.parent_off && shred->data.parent_off<=shred->slot-input->root_slot;
363 145795 : if( ok ) fd_repair_shred_insert( repair, shred, fd_type_pun_const( &rec->mr ), 0L );
364 145794 : }
365 4557 : if( ok ) fd_repair_fec_complete( repair, shred, fd_type_pun_const( &rec->mr ), 0, 0U, 0U, 0U, 0U, 0U, 0U, 0L );
366 :
367 : /* Repair invalidates a slot for a bad block header, conflicting
368 : parents or an equivocating FEC set, and never delivers it. */
369 4556 : if( fd_repair_slot_meta( repair, rec->slot )->invalidated ) {
370 0 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
371 0 : }
372 :
373 : /* Step 4: pop repair's reasm deque and fd_sched_fec_ingest() while
374 : there are delivered FEC sets remaining. */
375 9113 : while( !fd_repair_deque_empty( repair->reasm_deque ) ) {
376 4557 : fd_repair_deque_t out = fd_repair_deque_pop_head( repair->reasm_deque );
377 4557 : fd_repair_blk_t const * blk = fd_repair_blk_pool_ele_const( repair->blk_pool, out.blk_idx );
378 4557 : fd_repair_fec_t const * fec = fd_repair_fec_pool_ele_const( repair->fec_pool, blk->fecs[ out.fec_idx ] );
379 :
380 : /* Query our "store"-adjacent structure (completed) for the FEC
381 : set payload */
382 4557 : fd_shred_completed_fec_t * popped_rec = NULL;
383 13311 : for( ulong j=0UL; j<completed_cnt; j++ ) {
384 13311 : if( FD_LIKELY( !memcmp( completed[ j ].mr.hash, fec->mr.mr32.uc, sizeof(fd_hash_t) ) ) ) {
385 4557 : popped_rec = &completed[ j ];
386 4557 : break;
387 4557 : }
388 13311 : }
389 4557 : FD_TEST( popped_rec );
390 :
391 : /* Match Agave's model of only parsing deshreddable batches.
392 : Otherwise, Firedancer's eager per-FEC parsing might reject a
393 : block for a bad FEC set that Agave simply doesn't parse at all.
394 : A FEC set is deshreddable iff some FEC set at or after it in
395 : the same slot's chain carries DATA_COMPLETE. Complete batches
396 : are still fed one FEC set at a time. Only the final,
397 : potentially incomplete batch is held back. fec_set_results are captured
398 : from the completed sets above, independent of the scheduler. */
399 4557 : int deshreddable = 0;
400 13311 : for( ulong j=0UL; j<completed_cnt; j++ ) {
401 13311 : if( completed[ j ].chained && completed[ j ].slot==popped_rec->slot && completed[ j ].fec_set_idx>=popped_rec->fec_set_idx && completed[ j ].data_complete ) {
402 4557 : deshreddable = 1;
403 4557 : break;
404 4557 : }
405 13311 : }
406 4557 : if( !deshreddable ) continue;
407 :
408 : /* Bank lineage comes from the repair blk tree. Repair delivers a
409 : blk only after its parent blk in full, so the parent's bank_idx
410 : is known unless its batches were held back above. The blk's
411 : first FEC set starts a new bank, later FEC sets reuse the blk's.
412 : With an UpdateParent the first FEC set is init_fec_cnt, as in
413 : replay: FEC sets before it are the abandoned prefix, and a bank
414 : already built on the prefix is given up, not reused. */
415 4557 : if( out.fec_idx<blk->init_fec_cnt ) continue; /* the prefix, after repair confirmed the UpdateParent */
416 4557 : uint parent_bank_idx;
417 4557 : if( out.fec_idx==blk->init_fec_cnt ) {
418 1001 : parent_bank_idx = blk_bank_idx[ blk->parent ];
419 1001 : if( parent_bank_idx==UINT_MAX ) continue;
420 1001 : if( blk_bank_idx[ out.blk_idx ]!=UINT_MAX ) fd_sched_block_abandon( sched, blk_bank_idx[ out.blk_idx ], FD_SCHED_ABANDON_DISCARDED ); /* the optimistic prefix bank */
421 1001 : blk_bank_idx[ out.blk_idx ] = next_bank_idx++;
422 3556 : } else {
423 3556 : parent_bank_idx = blk_bank_idx[ out.blk_idx ];
424 3556 : if( parent_bank_idx==UINT_MAX ) continue;
425 3556 : }
426 4557 : uint bank_idx = blk_bank_idx[ out.blk_idx ];
427 :
428 4557 : fd_store_fec_t store_fec[1] = {0};
429 4557 : store_fec->key = popped_rec->mr;
430 4557 : FD_TEST( popped_rec->payload_sz<=UINT_MAX );
431 4557 : store_fec->data_sz = (uint)popped_rec->payload_sz;
432 4557 : memcpy( store_fec->shred_sz, popped_rec->shred_sz, sizeof(store_fec->shred_sz) );
433 :
434 4557 : fd_sched_fec_t sched_fec = {
435 4557 : .bank_idx = bank_idx,
436 4557 : .parent_bank_idx = parent_bank_idx,
437 4557 : .slot = blk->slot,
438 4557 : .parent_slot = blk->parent_slot,
439 4557 : .fec = store_fec,
440 4557 : .data = popped_rec->payload,
441 4557 : .shred_cnt = popped_rec->shred_cnt,
442 4557 : .is_last_in_batch = !!popped_rec->data_complete,
443 4557 : .is_last_in_block = out.fec_idx+1U==blk->cmpl_fec_cnt,
444 4557 : .is_first_in_block = out.fec_idx==blk->init_fec_cnt,
445 4557 : .starts_at_update_parent = out.fec_idx==blk->init_fec_cnt && blk->init_fec_cnt!=0U
446 4557 : };
447 :
448 : /* Drain abandoned blocks; fd_sched_fec_ingest requires an empty
449 : ref_q. The replay tile drains the same queue (to decrement bank
450 : refcounts); the harness has no banks, so it just discards. */
451 4557 : while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {}
452 :
453 : /* Final step: ingest the completed FEC set. */
454 4557 : FD_TEST( fd_sched_fec_can_ingest( sched, &sched_fec ) );
455 4557 : if( !fd_sched_fec_ingest( sched, &sched_fec ) ) {
456 2 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
457 4555 : } else {
458 : /* Harness stops at FEC ingest, so verify this slot's tick
459 : window here. Only complete batches reach this point, so
460 : this matches Agave's model of only verifying deshredded
461 : batches. */
462 4555 : if( fd_sched_block_verify_ticks( sched, bank_idx, SLOT_MAX_TICK_HEIGHT-1UL, SLOT_MAX_TICK_HEIGHT ) ) {
463 1486 : effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
464 1486 : }
465 4555 : }
466 4557 : }
467 4556 : }
468 :
469 : /* Finalize scratch allocation and return encoded effects span. */
470 1006 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
471 1006 : *output = effects;
472 1006 : return actual_end - (ulong)output_buf;
473 1006 : }
|