Line data Source code
1 : #include "fd_funk_private.h"
2 : #include "../util/racesan/fd_racesan_target.h"
3 :
4 : /* Provide the actual record map implementation */
5 :
6 : #define POOL_NAME fd_funk_rec_pool
7 1005 : #define POOL_ELE_T fd_funk_rec_t
8 : #define POOL_IDX_T uint
9 141 : #define POOL_NEXT map_next
10 : #define POOL_IMPL_STYLE 2
11 : #define POOL_LAZY 1
12 : #include "../util/tmpl/fd_pool_para.c"
13 :
14 : #define MAP_NAME fd_funk_rec_map
15 687 : #define MAP_ELE_T fd_funk_rec_t
16 6 : #define MAP_KEY_T fd_funk_xid_key_pair_t
17 114 : #define MAP_KEY pair
18 : #define MAP_KEY_EQ(k0,k1) fd_funk_xid_key_pair_eq((k0),(k1))
19 : #define MAP_KEY_HASH(k0,seed) fd_funk_xid_key_pair_hash((k0),(seed))
20 129 : #define MAP_IDX_T uint
21 273 : #define MAP_NEXT map_next
22 111 : #define MAP_MAGIC (0xf173da2ce77ecdb0UL) /* Firedancer rec db version 0 */
23 402 : #define MAP_CNT_WIDTH FD_FUNK_REC_MAP_CNT_WIDTH
24 : #define MAP_IMPL_STYLE 2
25 : #define MAP_PEDANTIC 1
26 : #include "../util/tmpl/fd_map_chain_para.c"
27 :
28 : static fd_funk_txn_t *
29 : fd_funk_rec_txn_borrow( fd_funk_t const * funk,
30 : fd_funk_txn_xid_t const * xid,
31 4194 : fd_funk_txn_map_query_t * query ) {
32 4194 : memset( query, 0, sizeof(fd_funk_txn_map_query_t) );
33 4194 : if( fd_funk_txn_xid_eq( xid, funk->shmem->last_publish ) ) return NULL;
34 :
35 4188 : fd_funk_txn_map_query_t txn_query[1];
36 4188 : for(;;) {
37 4188 : int txn_query_err = fd_funk_txn_map_query_try( funk->txn_map, xid, NULL, txn_query, 0 );
38 4188 : if( FD_UNLIKELY( txn_query_err==FD_MAP_ERR_AGAIN ) ) continue;
39 4188 : if( FD_UNLIKELY( txn_query_err!=FD_MAP_SUCCESS ) ) {
40 0 : FD_LOG_CRIT(( "fd_funk_rec op failed: txn_map_query_try(%lu:%lu) error %i-%s",
41 0 : xid->ul[0], xid->ul[1],
42 0 : txn_query_err, fd_map_strerror( txn_query_err ) ));
43 0 : }
44 4188 : break;
45 4188 : }
46 4188 : fd_funk_txn_t * txn = fd_funk_txn_map_query_ele( txn_query );
47 4188 : uint txn_state = FD_VOLATILE_CONST( txn->state );
48 4188 : if( FD_UNLIKELY( txn_state!=FD_FUNK_TXN_STATE_ACTIVE ) ) {
49 0 : FD_LOG_CRIT(( "fd_funk_rec op failed: txn %p %lu:%lu state is %u-%s",
50 0 : (void *)txn, xid->ul[0], xid->ul[1],
51 0 : txn_state, fd_funk_txn_state_str( txn_state ) ));
52 0 : }
53 4188 : return txn;
54 4188 : }
55 :
56 : static void
57 4194 : fd_funk_rec_txn_release( fd_funk_txn_map_query_t const * query ) {
58 4194 : if( !query->ele ) return;
59 0 : if( FD_UNLIKELY( fd_funk_txn_map_query_test( query )!=FD_MAP_SUCCESS ) ) {
60 0 : FD_LOG_CRIT(( "fd_funk_rec_txn_release: fd_funk_txn_map_query_test failed (data race detected?)" ));
61 0 : }
62 0 : }
63 :
64 : static void
65 : fd_funk_rec_key_set_pair( fd_funk_xid_key_pair_t * key_pair,
66 : fd_funk_txn_xid_t const * xid,
67 4194 : fd_funk_rec_key_t const * key ) {
68 4194 : fd_funk_txn_xid_copy( key_pair->xid, xid );
69 4194 : fd_funk_rec_key_copy( key_pair->key, key );
70 4194 : }
71 :
72 : fd_funk_rec_t *
73 : fd_funk_rec_query_try( fd_funk_t * funk,
74 : fd_funk_txn_xid_t const * xid,
75 : fd_funk_rec_key_t const * key,
76 0 : fd_funk_rec_query_t * query ) {
77 0 : if( FD_UNLIKELY( !funk ) ) FD_LOG_CRIT(( "NULL funk" ));
78 0 : if( FD_UNLIKELY( !xid ) ) FD_LOG_CRIT(( "NULL xid" ));
79 0 : if( FD_UNLIKELY( !key ) ) FD_LOG_CRIT(( "NULL key" ));
80 0 : if( FD_UNLIKELY( !query ) ) FD_LOG_CRIT(( "NULL query" ));
81 :
82 0 : fd_funk_xid_key_pair_t pair[1];
83 0 : if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid, funk->shmem->last_publish ) ) ) {
84 0 : fd_funk_txn_xid_set_root( pair->xid );
85 0 : } else {
86 0 : fd_funk_txn_xid_copy( pair->xid, xid );
87 0 : }
88 0 : fd_funk_rec_key_copy( pair->key, key );
89 :
90 0 : for(;;) {
91 0 : int err = fd_funk_rec_map_query_try( funk->rec_map, pair, NULL, query, 0 );
92 0 : if( err == FD_MAP_SUCCESS ) break;
93 0 : if( err == FD_MAP_ERR_KEY ) return NULL;
94 0 : if( err == FD_MAP_ERR_AGAIN ) continue;
95 0 : FD_LOG_CRIT(( "query returned err %d", err ));
96 0 : }
97 0 : return fd_funk_rec_map_query_ele( query );
98 0 : }
99 :
100 : fd_funk_rec_t const *
101 : fd_funk_rec_query_try_global( fd_funk_t const * funk,
102 : fd_funk_txn_xid_t const * xid,
103 : fd_funk_rec_key_t const * key,
104 : fd_funk_txn_xid_t * txn_out,
105 4194 : fd_funk_rec_query_t * query ) {
106 4194 : if( FD_UNLIKELY( !funk ) ) FD_LOG_CRIT(( "NULL funk" ));
107 4194 : if( FD_UNLIKELY( !key ) ) FD_LOG_CRIT(( "NULL key" ));
108 4194 : if( FD_UNLIKELY( !query ) ) FD_LOG_CRIT(( "NULL query" ));
109 :
110 : /* Attach to the funk transaction */
111 :
112 4194 : fd_funk_txn_map_query_t txn_query[1];
113 4194 : fd_funk_txn_t * txn = fd_funk_rec_txn_borrow( funk, xid, txn_query );
114 :
115 : /* Look for the first element in the hash chain with the right
116 : record key. This takes advantage of the fact that elements with
117 : the same record key appear on the same hash chain in order of
118 : newest to oldest. */
119 :
120 4194 : fd_funk_xid_key_pair_t pair[1];
121 4194 : fd_funk_rec_key_set_pair( pair, xid, key );
122 :
123 4194 : fd_funk_rec_map_t const * rec_map = funk->rec_map;
124 :
125 4194 : ulong hash = fd_funk_rec_map_key_hash( pair, rec_map->map->seed );
126 4194 : ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
127 4194 : fd_funk_rec_map_shmem_private_chain_t * chain = fd_funk_rec_map_shmem_private_chain( rec_map->map, hash );
128 :
129 : /* Start a speculative record map transaction */
130 :
131 4194 : struct {
132 4194 : fd_funk_rec_map_txn_t txn;
133 4194 : fd_funk_rec_map_txn_private_info_t lock[1];
134 4194 : } txn_mem;
135 4194 : fd_funk_rec_map_txn_t * rec_txn;
136 4194 : fd_funk_rec_t const * res;
137 :
138 4194 : retry:
139 4194 : res = NULL;
140 4194 : rec_txn = fd_funk_rec_map_txn_init( &txn_mem.txn, (void *)rec_map, 1UL );
141 4194 : txn_mem.lock[ 0 ].chain = chain;
142 4194 : txn_mem.txn.spec_cnt++;
143 4194 : if( FD_UNLIKELY( fd_funk_rec_map_txn_try( rec_txn, FD_MAP_FLAG_BLOCKING )!=FD_MAP_SUCCESS ) ) {
144 0 : FD_LOG_CRIT(( "fd_funk_rec_map_txn_try failed" ));
145 0 : }
146 :
147 4194 : query->ele = NULL;
148 4194 : query->chain = chain;
149 4194 : query->ver_cnt = txn_mem.lock[0].ver_cnt;
150 :
151 4194 : for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( funk->rec_map, chain_idx );
152 4197 : !fd_funk_rec_map_iter_done( iter );
153 4194 : iter = fd_funk_rec_map_iter_next( iter ) ) {
154 522 : fd_funk_rec_t const * ele = fd_funk_rec_map_iter_ele_const( iter );
155 522 : if( FD_LIKELY( fd_funk_rec_key_eq( key, ele->pair.key ) ) ) {
156 :
157 : /* For cur_txn in path from [txn] to [root] where root is NULL */
158 :
159 540 : for( fd_funk_txn_t const * cur_txn = txn; ; cur_txn = fd_funk_txn_parent( cur_txn, funk->txn_pool ) ) {
160 : /* If record ele is part of transaction cur_txn, we have a
161 : match. According to the property above, this will be the
162 : youngest descendent in the transaction stack. */
163 :
164 540 : int match = FD_UNLIKELY( cur_txn ) ? /* opt for root find (FIXME: eliminate branch with cmov into txn_xid_eq?) */
165 534 : fd_funk_txn_xid_eq( &cur_txn->xid, ele->pair.xid ) :
166 540 : fd_funk_txn_xid_eq_root( ele->pair.xid );
167 :
168 540 : if( FD_LIKELY( match ) ) {
169 519 : if( txn_out ) {
170 42 : *txn_out = *( cur_txn ? &cur_txn->xid : fd_funk_last_publish( funk ) );
171 42 : }
172 519 : query->ele = (fd_funk_rec_t *)ele;
173 519 : res = query->ele;
174 519 : goto found;
175 519 : }
176 :
177 21 : if( cur_txn == NULL ) break;
178 21 : }
179 522 : }
180 522 : }
181 :
182 4194 : found:
183 4194 : if( FD_UNLIKELY( fd_funk_rec_map_txn_test( rec_txn )!=FD_MAP_SUCCESS ) ) goto retry;
184 4194 : if( FD_LIKELY( txn ) ) fd_funk_txn_xid_assert( txn, pair->xid );
185 4194 : fd_funk_rec_txn_release( txn_query );
186 4194 : return res;
187 4194 : }
188 :
189 : fd_funk_rec_t *
190 : fd_funk_rec_prepare( fd_funk_t * funk,
191 : fd_funk_txn_xid_t const * xid,
192 : fd_funk_rec_key_t const * key,
193 : fd_funk_rec_prepare_t * prepare,
194 0 : int * opt_err ) {
195 0 : if( FD_UNLIKELY( !funk ) ) FD_LOG_CRIT(( "NULL funk" ));
196 0 : if( FD_UNLIKELY( !xid ) ) FD_LOG_CRIT(( "NULL xid" ));
197 0 : if( FD_UNLIKELY( !prepare ) ) FD_LOG_CRIT(( "NULL prepare" ));
198 :
199 0 : fd_funk_txn_map_query_t txn_query[1];
200 0 : fd_funk_txn_t * txn = fd_funk_rec_txn_borrow( funk, xid, txn_query );
201 :
202 0 : memset( prepare, 0, sizeof(fd_funk_rec_prepare_t) );
203 :
204 0 : if( !txn ) { /* Modifying last published */
205 0 : if( FD_UNLIKELY( fd_funk_last_publish_is_frozen( funk ) ) ) {
206 0 : fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_FROZEN );
207 0 : fd_funk_rec_txn_release( txn_query );
208 0 : return NULL;
209 0 : }
210 0 : } else {
211 0 : if( FD_UNLIKELY( fd_funk_txn_is_frozen( txn ) ) ) {
212 0 : fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_FROZEN );
213 0 : fd_funk_rec_txn_release( txn_query );
214 0 : return NULL;
215 0 : }
216 0 : }
217 :
218 0 : fd_funk_rec_t * rec = prepare->rec = fd_funk_rec_pool_acquire( funk->rec_pool, NULL, 1, opt_err );
219 0 : if( opt_err && *opt_err == FD_POOL_ERR_CORRUPT ) {
220 0 : FD_LOG_CRIT(( "corrupt element returned from funk rec pool" ));
221 0 : }
222 0 : if( FD_UNLIKELY( !rec ) ) {
223 0 : fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_REC );
224 0 : fd_funk_rec_txn_release( txn_query );
225 0 : return rec;
226 0 : }
227 :
228 0 : fd_funk_val_init( rec );
229 0 : if( txn == NULL ) {
230 0 : fd_funk_txn_xid_set_root( rec->pair.xid );
231 0 : } else {
232 0 : fd_funk_txn_xid_copy( rec->pair.xid, &txn->xid );
233 0 : prepare->rec_head_idx = &txn->rec_head_idx;
234 0 : prepare->rec_tail_idx = &txn->rec_tail_idx;
235 0 : }
236 0 : fd_funk_rec_key_copy( rec->pair.key, key );
237 0 : rec->tag = 0;
238 0 : rec->prev_idx = FD_FUNK_REC_IDX_NULL;
239 0 : rec->next_idx = FD_FUNK_REC_IDX_NULL;
240 0 : fd_funk_rec_txn_release( txn_query );
241 0 : return rec;
242 0 : }
243 :
244 : #if FD_HAS_ATOMIC
245 :
246 : static void
247 : fd_funk_rec_push_tail( fd_funk_t * funk,
248 45 : fd_funk_rec_prepare_t * prepare ) {
249 45 : fd_funk_rec_t * rec = prepare->rec;
250 45 : uint rec_idx = (uint)( rec - funk->rec_pool->ele );
251 45 : uint * rec_head_idx = prepare->rec_head_idx;
252 45 : uint * rec_tail_idx = prepare->rec_tail_idx;
253 :
254 45 : for(;;) {
255 :
256 : /* Doubly linked list append. Robust in the event of concurrent
257 : publishes. Iteration during publish not supported. Sequence:
258 : - Identify tail element
259 : - Set new element's prev and next pointers
260 : - Set tail element's next pointer
261 : - Set tail pointer */
262 :
263 45 : uint rec_prev_idx = FD_VOLATILE_CONST( *rec_tail_idx );
264 45 : rec->prev_idx = rec_prev_idx;
265 45 : FD_COMPILER_MFENCE();
266 :
267 45 : uint * next_idx_p;
268 45 : if( fd_funk_rec_idx_is_null( rec_prev_idx ) ) {
269 45 : next_idx_p = rec_head_idx;
270 45 : } else {
271 0 : next_idx_p = &funk->rec_pool->ele[ rec_prev_idx ].next_idx;
272 0 : }
273 :
274 45 : fd_racesan_hook( "funk_rec_push_tail:next_cas" );
275 45 : if( FD_UNLIKELY( !__sync_bool_compare_and_swap( next_idx_p, FD_FUNK_REC_IDX_NULL, rec_idx ) ) ) {
276 : /* Another thread beat us to the punch */
277 0 : FD_SPIN_PAUSE();
278 0 : continue;
279 0 : }
280 :
281 45 : fd_racesan_hook( "funk_rec_push_tail:tail_cas" );
282 45 : if( FD_UNLIKELY( !__sync_bool_compare_and_swap( rec_tail_idx, rec_prev_idx, rec_idx ) ) ) {
283 : /* This CAS is guaranteed to succeed if the previous CAS passed. */
284 0 : FD_LOG_CRIT(( "Irrecoverable data race encountered while appending to txn rec list (invariant violation?): cas(%p,%u,%u)",
285 0 : (void *)rec_tail_idx, rec_prev_idx, rec_idx ));
286 0 : }
287 :
288 45 : break;
289 45 : }
290 45 : }
291 :
292 : #else
293 :
294 : static void
295 : fd_funk_rec_push_tail( fd_funk_t * funk,
296 : fd_funk_rec_prepare_t * prepare ) {
297 : fd_funk_rec_t * rec = prepare->rec;
298 : uint rec_idx = (uint)( rec - funk->rec_pool->ele );
299 : uint * rec_head_idx = prepare->rec_head_idx;
300 : uint * rec_tail_idx = prepare->rec_tail_idx;
301 : uint rec_prev_idx = *rec_tail_idx;
302 : *rec_tail_idx = rec_idx;
303 : rec->prev_idx = rec_prev_idx;
304 : rec->next_idx = FD_FUNK_REC_IDX_NULL;
305 : if( fd_funk_rec_idx_is_null( rec_prev_idx ) ) {
306 : *rec_head_idx = rec_idx;
307 : } else {
308 : funk->rec_pool->ele[ rec_prev_idx ].next_idx = rec_idx;
309 : }
310 : }
311 :
312 : #endif
313 :
314 : void
315 : fd_funk_rec_publish( fd_funk_t * funk,
316 45 : fd_funk_rec_prepare_t * prepare ) {
317 45 : fd_funk_rec_t * rec = prepare->rec;
318 45 : rec->prev_idx = FD_FUNK_REC_IDX_NULL;
319 45 : rec->next_idx = FD_FUNK_REC_IDX_NULL;
320 :
321 45 : if( prepare->rec_head_idx ) {
322 45 : fd_funk_rec_push_tail( funk, prepare );
323 45 : }
324 :
325 45 : fd_racesan_hook( "funk_rec_publish:map_insert" );
326 45 : int insert_err = fd_funk_rec_map_insert( funk->rec_map, rec, FD_MAP_FLAG_BLOCKING );
327 45 : if( insert_err ) {
328 0 : FD_LOG_CRIT(( "fd_funk_rec_map_insert failed (%i-%s)", insert_err, fd_map_strerror( insert_err ) ));
329 0 : }
330 45 : }
331 :
332 : int
333 249 : fd_funk_rec_verify( fd_funk_t * funk ) {
334 249 : fd_funk_rec_map_t * rec_map = funk->rec_map;
335 249 : fd_funk_rec_pool_t * rec_pool = funk->rec_pool;
336 249 : ulong rec_max = fd_funk_rec_pool_ele_max( rec_pool );
337 :
338 838272 : # define TEST(c) do { \
339 838272 : if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
340 838272 : } while(0)
341 :
342 249 : TEST( !fd_funk_rec_map_verify( rec_map ) );
343 249 : TEST( !fd_funk_rec_pool_verify( rec_pool ) );
344 :
345 : /* Iterate (again) over all records in use */
346 :
347 249 : fd_funk_rec_map_shmem_private_chain_t * chain_tbl =
348 249 : fd_funk_rec_map_shmem_private_chain( rec_map->map, 0UL );
349 249 : ulong chain_cnt = fd_funk_rec_map_chain_cnt( rec_map );
350 837999 : for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
351 837750 : ulong chain_ele_cnt = fd_funk_rec_map_private_vcnt_cnt( chain_tbl[ chain_idx ].ver_cnt );
352 837750 : ulong chain_ele_idx = 0UL;
353 837750 : for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( rec_map, chain_idx );
354 837756 : !fd_funk_rec_map_iter_done( iter );
355 837750 : iter = fd_funk_rec_map_iter_next( iter ), chain_ele_idx++ ) {
356 6 : fd_funk_rec_t const * rec = fd_funk_rec_map_iter_ele_const( iter );
357 :
358 : /* Make sure every record either links up with the last published
359 : transaction or an in-prep transaction and the flags are sane. */
360 :
361 6 : fd_funk_txn_xid_t const * xid = fd_funk_rec_xid( rec );
362 :
363 6 : if( fd_funk_txn_xid_eq_root( xid ) ) { /* This is a record from the last published transaction */
364 :
365 6 : TEST( fd_funk_txn_xid_eq_root( xid ) );
366 : /* No record linked list at the root txn */
367 6 : TEST( fd_funk_rec_idx_is_null( rec->prev_idx ) );
368 6 : TEST( fd_funk_rec_idx_is_null( rec->next_idx ) );
369 :
370 6 : } else { /* This is a record from an in-prep transaction */
371 :
372 0 : TEST( fd_funk_txn_query( xid, funk->txn_map ) );
373 :
374 0 : }
375 6 : }
376 837750 : TEST( chain_ele_idx==chain_ele_cnt );
377 837750 : }
378 :
379 : /* Clear record tags and then verify membership */
380 :
381 1675737 : for( ulong rec_idx=0UL; rec_idx<rec_max; rec_idx++ ) rec_pool->ele[ rec_idx ].tag = 0;
382 :
383 249 : do {
384 249 : fd_funk_all_iter_t iter[1];
385 255 : for( fd_funk_all_iter_new( funk, iter ); !fd_funk_all_iter_done( iter ); fd_funk_all_iter_next( iter ) ) {
386 6 : fd_funk_rec_t * rec = fd_funk_all_iter_ele( iter );
387 6 : if( fd_funk_txn_xid_eq_root( rec->pair.xid ) ) {
388 6 : TEST( !rec->tag );
389 6 : rec->tag = 1;
390 6 : }
391 6 : }
392 :
393 249 : fd_funk_txn_all_iter_t txn_iter[1];
394 795 : for( fd_funk_txn_all_iter_new( funk, txn_iter ); !fd_funk_txn_all_iter_done( txn_iter ); fd_funk_txn_all_iter_next( txn_iter ) ) {
395 546 : fd_funk_txn_t const * txn = fd_funk_txn_all_iter_ele_const( txn_iter );
396 :
397 546 : uint rec_idx = txn->rec_head_idx;
398 546 : while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
399 0 : TEST( (rec_idx<rec_max) && !rec_pool->ele[ rec_idx ].tag );
400 0 : rec_pool->ele[ rec_idx ].tag = 1;
401 0 : fd_funk_rec_query_t query[1];
402 0 : fd_funk_rec_t const * rec2 = fd_funk_rec_query_try( funk, &txn->xid, rec_pool->ele[ rec_idx ].pair.key, query );
403 0 : TEST( rec2 == rec_pool->ele + rec_idx );
404 0 : uint next_idx = rec_pool->ele[ rec_idx ].next_idx;
405 0 : if( !fd_funk_rec_idx_is_null( next_idx ) ) TEST( rec_pool->ele[ next_idx ].prev_idx==rec_idx );
406 0 : rec_idx = next_idx;
407 0 : }
408 546 : }
409 249 : } while(0);
410 :
411 249 : do {
412 249 : fd_funk_txn_all_iter_t txn_iter[1];
413 795 : for( fd_funk_txn_all_iter_new( funk, txn_iter ); !fd_funk_txn_all_iter_done( txn_iter ); fd_funk_txn_all_iter_next( txn_iter ) ) {
414 546 : fd_funk_txn_t const * txn = fd_funk_txn_all_iter_ele_const( txn_iter );
415 :
416 546 : uint rec_idx = txn->rec_tail_idx;
417 546 : while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
418 0 : TEST( (rec_idx<rec_max) && rec_pool->ele[ rec_idx ].tag );
419 0 : rec_pool->ele[ rec_idx ].tag = 0;
420 0 : uint prev_idx = rec_pool->ele[ rec_idx ].prev_idx;
421 0 : if( !fd_funk_rec_idx_is_null( prev_idx ) ) TEST( rec_pool->ele[ prev_idx ].next_idx==rec_idx );
422 0 : rec_idx = prev_idx;
423 0 : }
424 546 : }
425 249 : } while(0);
426 :
427 249 : fd_funk_all_iter_t iter[1];
428 255 : for( fd_funk_all_iter_new( funk, iter ); !fd_funk_all_iter_done( iter ); fd_funk_all_iter_next( iter ) ) {
429 6 : fd_funk_rec_t const * rec = fd_funk_all_iter_ele( iter );
430 6 : FD_TEST( rec->tag == fd_funk_txn_xid_eq_root( rec->pair.xid ) );
431 6 : }
432 :
433 249 : # undef TEST
434 :
435 249 : return FD_FUNK_SUCCESS;
436 249 : }
|