Line data Source code
1 : #include "fd_resolv_tile.h"
2 : #include "../../disco/fd_txn_m.h"
3 : #include "../../disco/topo/fd_topo.h"
4 : #include "../replay/fd_replay_tile.h"
5 : #include "generated/fd_resolv_tile_seccomp.h"
6 : #include "../../discof/fd_accdb_topo.h"
7 : #include "../../disco/metrics/fd_metrics.h"
8 : #include "../../flamenco/accdb/fd_accdb_sync.h"
9 : #include "../../flamenco/runtime/fd_alut_interp.h"
10 : #include "../../flamenco/runtime/fd_system_ids_pp.h"
11 : #include "../../flamenco/runtime/fd_bank.h"
12 : #include "../../util/pod/fd_pod_format.h"
13 :
14 : #if FD_HAS_AVX
15 : #include "../../util/simd/fd_avx.h"
16 : #endif
17 :
18 0 : #define IN_KIND_DEDUP (0)
19 0 : #define IN_KIND_REPLAY (1)
20 :
21 : struct blockhash {
22 : uchar b[ 32 ];
23 : };
24 :
25 : typedef struct blockhash blockhash_t;
26 :
27 : struct blockhash_map {
28 : blockhash_t key;
29 : ulong slot;
30 : };
31 :
32 : typedef struct blockhash_map blockhash_map_t;
33 :
34 : static const blockhash_t null_blockhash = { 0 };
35 :
36 : /* The blockhash ring holds recent blockhashes, so we can identify when
37 : a transaction arrives, what slot it will expire (and can no longer be
38 : packed) in. This is useful so we don't send transactions to pack
39 : that are no longer packable.
40 :
41 : Unfortunately, poorly written transaction senders frequently send
42 : transactions from millions of slots ago, so we need a large ring to
43 : be able to determine and evict these. The highest practically useful
44 : value here is around 22, which works out to 19 days of blockhash
45 : history. Beyond this, the validator is likely to be restarted, and
46 : lose the history anyway. */
47 :
48 0 : #define BLOCKHASH_LG_RING_CNT 22UL
49 0 : #define BLOCKHASH_RING_LEN (1UL<<BLOCKHASH_LG_RING_CNT)
50 :
51 : #define MAP_NAME map
52 0 : #define MAP_T blockhash_map_t
53 0 : #define MAP_KEY_T blockhash_t
54 0 : #define MAP_LG_SLOT_CNT (BLOCKHASH_LG_RING_CNT+1UL)
55 0 : #define MAP_KEY_NULL null_blockhash
56 : #if FD_HAS_AVX
57 0 : # define MAP_KEY_INVAL(k) _mm256_testz_si256( wb_ldu( (k).b ), wb_ldu( (k).b ) )
58 : #else
59 : # define MAP_KEY_INVAL(k) MAP_KEY_EQUAL(k, null_blockhash)
60 : #endif
61 0 : #define MAP_KEY_EQUAL(k0,k1) (!memcmp((k0).b,(k1).b, 32UL))
62 : #define MAP_MEMOIZE 0
63 : #define MAP_KEY_EQUAL_IS_SLOW 1
64 0 : #define MAP_KEY_HASH(key) fd_uint_load_4( (key).b )
65 : #define MAP_QUERY_OPT 1
66 :
67 : #include "../../util/tmpl/fd_map.c"
68 :
69 : typedef struct {
70 : union {
71 : ulong pool_next; /* Used when it's released */
72 : ulong lru_next; /* Used when it's acquired */
73 : }; /* .. so it's okay to store them in the same memory */
74 : ulong lru_prev;
75 :
76 : ulong map_next;
77 : ulong map_prev;
78 :
79 : blockhash_t * blockhash;
80 : uchar _[ FD_TPU_PARSED_MTU ] __attribute__((aligned(alignof(fd_txn_m_t))));
81 : } fd_stashed_txn_m_t;
82 :
83 : #define POOL_NAME pool
84 0 : #define POOL_T fd_stashed_txn_m_t
85 0 : #define POOL_NEXT pool_next
86 : #define POOL_IDX_T ulong
87 :
88 : #include "../../util/tmpl/fd_pool.c"
89 :
90 : /* We'll push at the head, which means the tail is the oldest. */
91 : #define DLIST_NAME lru_list
92 : #define DLIST_ELE_T fd_stashed_txn_m_t
93 0 : #define DLIST_PREV lru_prev
94 0 : #define DLIST_NEXT lru_next
95 :
96 : #include "../../util/tmpl/fd_dlist.c"
97 :
98 : #define MAP_NAME map_chain
99 0 : #define MAP_ELE_T fd_stashed_txn_m_t
100 : #define MAP_KEY_T blockhash_t *
101 0 : #define MAP_KEY blockhash
102 0 : #define MAP_IDX_T ulong
103 0 : #define MAP_NEXT map_next
104 0 : #define MAP_PREV map_prev
105 0 : #define MAP_KEY_HASH(k,s) ((s) ^ fd_ulong_load_8( (*(k))->b ))
106 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp((*(k0))->b, (*(k1))->b, 32UL))
107 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
108 : #define MAP_MULTI 1
109 :
110 : #include "../../util/tmpl/fd_map_chain.c"
111 :
112 : typedef struct {
113 : int kind;
114 :
115 : fd_wksp_t * mem;
116 : ulong chunk0;
117 : ulong wmark;
118 : ulong mtu;
119 : } fd_resolv_in_ctx_t;
120 :
121 : typedef struct {
122 : fd_wksp_t * mem;
123 : ulong chunk0;
124 : ulong wmark;
125 : ulong chunk;
126 : } fd_resolv_out_ctx_t;
127 :
128 : typedef struct {
129 : ulong round_robin_idx;
130 : ulong round_robin_cnt;
131 :
132 : int bundle_failed;
133 : ulong bundle_id;
134 :
135 : blockhash_map_t * blockhash_map;
136 :
137 : ulong flushing_slot;
138 : ulong flush_pool_idx;
139 :
140 : /* In the full client, the resolv tile is passed only a rooted bank
141 : index from replay whenever the root is advanced.
142 :
143 : This is enough to query the accounts database for that bank and
144 : retrieve the address lookup tables. Because of lifetime concerns
145 : around bank ownership, the replay tile is solely responsible for
146 : freeing the bank when it is no longer needed. To facilitate this,
147 : the resolv tile sends a message to replay when it is done with a
148 : rooted bank (after exchanging it for a new rooted bank). */
149 : fd_banks_t banks[1];
150 : fd_bank_t bank[1];
151 :
152 : fd_accdb_user_t accdb[1];
153 :
154 : fd_stashed_txn_m_t * pool;
155 : map_chain_t * map_chain;
156 : lru_list_t lru_list[1];
157 :
158 : ulong completed_slot;
159 : ulong blockhash_ring_idx;
160 : blockhash_t blockhash_ring[ BLOCKHASH_RING_LEN ];
161 :
162 : fd_replay_root_advanced_t _rooted_slot_msg;
163 : fd_replay_slot_completed_t _completed_slot_msg;
164 :
165 : struct {
166 : ulong lut[ FD_METRICS_COUNTER_RESOLV_LUT_RESOLVED_CNT ];
167 : ulong blockhash_expired;
168 : ulong bundle_peer_failure;
169 : ulong stash[ FD_METRICS_COUNTER_RESOLV_STASH_OPERATION_CNT ];
170 : } metrics;
171 :
172 : fd_resolv_in_ctx_t in[ 64UL ];
173 :
174 : fd_resolv_out_ctx_t out_pack[ 1UL ];
175 : fd_resolv_out_ctx_t out_replay[ 1UL ];
176 : } fd_resolv_ctx_t;
177 :
178 : FD_FN_CONST static inline ulong
179 0 : scratch_align( void ) {
180 0 : return alignof( fd_resolv_ctx_t );
181 0 : }
182 :
183 : FD_FN_PURE static inline ulong
184 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
185 0 : (void)tile;
186 0 : ulong l = FD_LAYOUT_INIT;
187 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_resolv_ctx_t ), sizeof( fd_resolv_ctx_t ) );
188 0 : l = FD_LAYOUT_APPEND( l, pool_align(), pool_footprint ( 1UL<<16UL ) );
189 0 : l = FD_LAYOUT_APPEND( l, map_chain_align(), map_chain_footprint( 8192UL ) );
190 0 : l = FD_LAYOUT_APPEND( l, map_align(), map_footprint() );
191 0 : return FD_LAYOUT_FINI( l, scratch_align() );
192 0 : }
193 :
194 : static inline void
195 0 : metrics_write( fd_resolv_ctx_t * ctx ) {
196 0 : FD_MCNT_SET( RESOLF, BLOCKHASH_EXPIRED, ctx->metrics.blockhash_expired );
197 0 : FD_MCNT_ENUM_COPY( RESOLF, LUT_RESOLVED, ctx->metrics.lut );
198 0 : FD_MCNT_ENUM_COPY( RESOLF, STASH_OPERATION, ctx->metrics.stash );
199 0 : FD_MCNT_SET( RESOLF, TRANSACTION_BUNDLE_PEER_FAILURE, ctx->metrics.bundle_peer_failure );
200 0 : }
201 :
202 : static int
203 : before_frag( fd_resolv_ctx_t * ctx,
204 : ulong in_idx,
205 : ulong seq,
206 0 : ulong sig ) {
207 0 : (void)sig;
208 :
209 0 : if( FD_UNLIKELY( ctx->in[in_idx].kind==IN_KIND_REPLAY ) ) return 0;
210 :
211 0 : return (seq % ctx->round_robin_cnt) != ctx->round_robin_idx;
212 0 : }
213 :
214 : static inline void
215 : during_frag( fd_resolv_ctx_t * ctx,
216 : ulong in_idx,
217 : ulong seq FD_PARAM_UNUSED,
218 : ulong sig FD_PARAM_UNUSED,
219 : ulong chunk,
220 : ulong sz,
221 0 : ulong ctl FD_PARAM_UNUSED ) {
222 :
223 0 : if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) )
224 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
225 :
226 0 : switch( ctx->in[in_idx].kind ) {
227 0 : case IN_KIND_DEDUP: {
228 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
229 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
230 0 : fd_memcpy( dst, src, sz );
231 0 : break;
232 0 : }
233 0 : case IN_KIND_REPLAY: {
234 0 : if( FD_UNLIKELY( sig==REPLAY_SIG_ROOT_ADVANCED ) ) {
235 0 : ctx->_rooted_slot_msg = *(fd_replay_root_advanced_t *)fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk );
236 0 : } else if( FD_UNLIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) {
237 0 : ctx->_completed_slot_msg = *(fd_replay_slot_completed_t *)fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk );
238 0 : }
239 0 : break;
240 0 : }
241 0 : default:
242 0 : FD_LOG_ERR(( "unknown in kind %d", ctx->in[in_idx].kind ));
243 0 : }
244 0 : }
245 :
246 : /* peek_alut reads a single address lookup table from database cache. */
247 :
248 : static int
249 : peek_alut( fd_resolv_ctx_t * ctx,
250 : fd_txn_m_t * txnm,
251 : fd_alut_interp_t * interp,
252 0 : ulong alut_idx ) {
253 0 : fd_funk_txn_xid_t const xid = { .ul = { fd_bank_slot_get( ctx->bank ), fd_bank_slot_get( ctx->bank ) } };
254 :
255 0 : fd_txn_t const * txn = fd_txn_m_txn_t_const ( txnm );
256 0 : uchar const * txn_payload = fd_txn_m_payload_const( txnm );
257 0 : fd_txn_acct_addr_lut_t const * addr_lut =
258 0 : &fd_txn_get_address_tables_const( txn )[ alut_idx ];
259 0 : fd_pubkey_t addr_lut_acc = FD_LOAD( fd_pubkey_t, txn_payload+addr_lut->addr_off );
260 :
261 0 : fd_accdb_ro_t ro[1];
262 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( ctx->accdb, ro, &xid, &addr_lut_acc ) ) ) {
263 0 : return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
264 0 : }
265 :
266 0 : int err = fd_alut_interp_next(
267 0 : interp,
268 0 : &addr_lut_acc,
269 0 : fd_accdb_ref_owner ( ro ),
270 0 : fd_accdb_ref_data_const( ro ),
271 0 : fd_accdb_ref_data_sz ( ro ) );
272 :
273 0 : fd_accdb_close_ro( ctx->accdb, ro );
274 :
275 0 : return err;
276 0 : }
277 :
278 : /* peek_aluts reads address lookup tables from database cache.
279 : Gracefully recovers from data races and missing accounts. */
280 :
281 : static int
282 : peek_aluts( fd_resolv_ctx_t * ctx,
283 0 : fd_txn_m_t * txnm ) {
284 :
285 : /* Unpack context */
286 0 : fd_txn_t const * txn = fd_txn_m_txn_t_const ( txnm );
287 0 : uchar const * txn_payload = fd_txn_m_payload_const( txnm );
288 0 : ulong const alut_cnt = txn->addr_table_lookup_cnt;
289 0 : ulong const slot = fd_bank_slot_get( ctx->bank );
290 0 : fd_sysvar_cache_t const * sysvar_cache = fd_bank_sysvar_cache_query( ctx->bank ); FD_TEST( sysvar_cache );
291 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( sysvar_cache );
292 :
293 : /* Write indirect addrs into here */
294 0 : fd_acct_addr_t * indir_addrs = fd_txn_m_alut( txnm );
295 :
296 0 : int err = FD_RUNTIME_EXECUTE_SUCCESS;
297 0 : fd_alut_interp_t interp[1];
298 0 : fd_alut_interp_new( interp, indir_addrs, txn, txn_payload, slot_hashes, slot );
299 0 : for( ulong i=0UL; i<alut_cnt; i++ ) {
300 0 : err = peek_alut( ctx, txnm, interp, i );
301 0 : if( FD_UNLIKELY( err ) ) break;
302 0 : }
303 0 : fd_alut_interp_delete( interp );
304 0 : fd_sysvar_cache_slot_hashes_leave_const( sysvar_cache, slot_hashes );
305 :
306 0 : ulong ctr_idx;
307 0 : switch( err ) {
308 0 : case FD_RUNTIME_EXECUTE_SUCCESS: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_SUCCESS_IDX; break;
309 0 : case FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_ACCOUNT_NOT_FOUND_IDX; break;
310 0 : case FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_INVALID_ACCOUNT_OWNER_IDX; break;
311 0 : case FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_INVALID_ACCOUNT_DATA_IDX; break;
312 0 : case FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_INVALID_LOOKUP_INDEX_IDX; break;
313 0 : default: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_ACCOUNT_UNINITIALIZED_IDX; break;
314 0 : }
315 0 : ctx->metrics.lut[ ctr_idx ]++;
316 0 : return err;
317 0 : }
318 :
319 : static int
320 : publish_txn( fd_resolv_ctx_t * ctx,
321 : fd_stem_context_t * stem,
322 0 : fd_stashed_txn_m_t const * stashed ) {
323 0 : fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
324 0 : fd_memcpy( txnm, stashed->_, fd_txn_m_realized_footprint( (fd_txn_m_t *)stashed->_, 1, 0 ) );
325 :
326 0 : fd_txn_t const * txnt = fd_txn_m_txn_t( txnm );
327 :
328 0 : txnm->reference_slot = ctx->flushing_slot;
329 :
330 0 : if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) {
331 0 : if( FD_UNLIKELY( !ctx->bank->data ) ) {
332 0 : FD_MCNT_INC( RESOLF, NO_BANK_DROP, 1 );
333 0 : return 0;
334 0 : }
335 0 : int err = peek_aluts( ctx, txnm );
336 0 : if( FD_UNLIKELY( err ) ) return 0;
337 0 : }
338 :
339 0 : ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 );
340 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
341 0 : fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, 0UL, tspub );
342 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
343 :
344 0 : return 1;
345 0 : }
346 :
347 : static inline void
348 : after_credit( fd_resolv_ctx_t * ctx,
349 : fd_stem_context_t * stem,
350 : int * opt_poll_in,
351 0 : int * charge_busy ) {
352 0 : if( FD_LIKELY( ctx->flush_pool_idx==ULONG_MAX ) ) return;
353 :
354 0 : *charge_busy = 1;
355 0 : *opt_poll_in = 0;
356 :
357 0 : ulong next = map_chain_idx_next_const( ctx->flush_pool_idx, ULONG_MAX, ctx->pool );
358 0 : map_chain_idx_remove_fast( ctx->map_chain, ctx->flush_pool_idx, ctx->pool );
359 0 : if( FD_LIKELY( publish_txn( ctx, stem, pool_ele( ctx->pool, ctx->flush_pool_idx ) ) ) ) {
360 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_PUBLISHED_IDX ]++;
361 0 : } else {
362 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_REMOVED_IDX ]++;
363 0 : }
364 0 : lru_list_idx_remove( ctx->lru_list, ctx->flush_pool_idx, ctx->pool );
365 0 : pool_idx_release( ctx->pool, ctx->flush_pool_idx );
366 0 : ctx->flush_pool_idx = next;
367 0 : }
368 :
369 : /* Returns 0 if not a durable nonce transaction and 1 if it may be a
370 : durable nonce transaction */
371 :
372 : FD_FN_PURE static inline int
373 : fd_resolv_is_durable_nonce( fd_txn_t const * txn,
374 0 : uchar const * payload ) {
375 0 : if( FD_UNLIKELY( txn->instr_cnt==0 ) ) return 0;
376 :
377 0 : fd_txn_instr_t const * ix0 = &txn->instr[ 0 ];
378 0 : fd_acct_addr_t const * prog0 = fd_txn_get_acct_addrs( txn, payload ) + ix0->program_id;
379 : /* First instruction must be SystemProgram nonceAdvance instruction */
380 0 : fd_acct_addr_t const system_program[1] = { { { SYS_PROG_ID } } };
381 0 : if( FD_LIKELY( memcmp( prog0, system_program, sizeof(fd_acct_addr_t) ) ) ) return 0;
382 :
383 : /* instruction with three accounts and a four byte instruction data, a
384 : little-endian uint value 4 */
385 0 : if( FD_UNLIKELY( (ix0->data_sz!=4) | (ix0->acct_cnt!=3) ) ) return 0;
386 :
387 0 : return fd_uint_load_4( payload + ix0->data_off )==4U;
388 0 : }
389 :
390 : static inline void
391 : after_frag( fd_resolv_ctx_t * ctx,
392 : ulong in_idx,
393 : ulong seq,
394 : ulong sig,
395 : ulong sz,
396 : ulong tsorig,
397 : ulong _tspub,
398 0 : fd_stem_context_t * stem ) {
399 0 : (void)seq;
400 0 : (void)sz;
401 0 : (void)_tspub;
402 :
403 0 : if( FD_UNLIKELY( ctx->in[in_idx].kind==IN_KIND_REPLAY ) ) {
404 0 : switch( sig ) {
405 0 : case REPLAY_SIG_SLOT_COMPLETED: {
406 0 : fd_replay_slot_completed_t const * msg = &ctx->_completed_slot_msg;
407 :
408 : /* blockhash_ring is initalized to all zeros. blockhash=0 is an illegal map query */
409 0 : if( FD_UNLIKELY( memcmp( &ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], (uchar[ 32UL ]){ 0UL }, sizeof(blockhash_t) ) ) ) {
410 0 : blockhash_map_t * entry = map_query( ctx->blockhash_map, ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], NULL );
411 0 : if( FD_LIKELY( entry ) ) map_remove( ctx->blockhash_map, entry );
412 0 : }
413 :
414 0 : memcpy( ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ].b, msg->block_hash.uc, 32UL );
415 0 : ctx->blockhash_ring_idx++;
416 :
417 0 : blockhash_map_t * blockhash = map_insert( ctx->blockhash_map, *(blockhash_t *)msg->block_hash.uc );
418 0 : blockhash->slot = msg->slot;
419 :
420 0 : blockhash_t * hash = (blockhash_t *)msg->block_hash.uc;
421 0 : ctx->flush_pool_idx = map_chain_idx_query_const( ctx->map_chain, &hash, ULONG_MAX, ctx->pool );
422 0 : ctx->flushing_slot = msg->slot;
423 :
424 0 : ctx->completed_slot = msg->slot;
425 0 : break;
426 0 : }
427 0 : case REPLAY_SIG_ROOT_ADVANCED: {
428 0 : fd_replay_root_advanced_t const * msg = &ctx->_rooted_slot_msg;
429 :
430 : /* Replace current bank with new bank */
431 0 : fd_bank_data_t * prev_bank = ctx->bank->data;
432 :
433 0 : FD_TEST( fd_banks_bank_query( ctx->bank, ctx->banks, msg->bank_idx ) );
434 :
435 : /* Send slot completed message back to replay, so it can
436 : decrement the reference count of the previous bank. */
437 0 : if( FD_LIKELY( prev_bank ) ) {
438 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
439 0 : fd_resolv_slot_exchanged_t * slot_exchanged =
440 0 : fd_type_pun( fd_chunk_to_laddr( ctx->out_replay->mem, ctx->out_replay->chunk ) );
441 0 : slot_exchanged->bank_idx = prev_bank->idx;
442 0 : fd_stem_publish( stem, 1UL, 0UL, ctx->out_replay->chunk, sizeof(fd_resolv_slot_exchanged_t), 0UL, tsorig, tspub );
443 0 : ctx->out_replay->chunk = fd_dcache_compact_next( ctx->out_replay->chunk, sizeof(fd_resolv_slot_exchanged_t), ctx->out_replay->chunk0, ctx->out_replay->wmark );
444 0 : }
445 :
446 0 : break;
447 0 : }
448 0 : default: break;
449 0 : }
450 0 : return;
451 0 : }
452 :
453 0 : fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
454 0 : FD_TEST( txnm->payload_sz<=FD_TPU_MTU );
455 0 : FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ );
456 0 : fd_txn_t const * txnt = fd_txn_m_txn_t( txnm );
457 :
458 : /* If we find the recent blockhash, life is simple. We drop
459 : transactions that couldn't possibly execute any more, and forward
460 : to pack ones that could.
461 :
462 : If we can't find the recent blockhash ... it means one of four
463 : things,
464 :
465 : (1) The blockhash is really old (more than 19 days) or just
466 : non-existent.
467 : (2) The blockhash is not that old, but was created before this
468 : validator was started.
469 : (3) It's really new (we haven't seen the bank yet).
470 : (4) It's a durable nonce transaction, or part of a bundle (just let
471 : it pass).
472 :
473 : For durable nonce transactions, there isn't much we can do except
474 : pass them along and see if they execute.
475 :
476 : For the other three cases ... we don't want to flood pack with what
477 : might be junk transactions, so we accumulate them into a local
478 : buffer. If we later see the blockhash come to exist, we forward any
479 : buffered transactions to back. */
480 :
481 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id && (txnm->block_engine.bundle_id!=ctx->bundle_id) ) ) {
482 0 : ctx->bundle_failed = 0;
483 0 : ctx->bundle_id = txnm->block_engine.bundle_id;
484 0 : }
485 :
486 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id && ctx->bundle_failed ) ) {
487 0 : ctx->metrics.bundle_peer_failure++;
488 0 : return;
489 0 : }
490 :
491 0 : txnm->reference_slot = ctx->completed_slot;
492 0 : blockhash_map_t const * blockhash = map_query_const( ctx->blockhash_map, *(blockhash_t*)( fd_txn_m_payload( txnm )+txnt->recent_blockhash_off ), NULL );
493 0 : if( FD_LIKELY( blockhash ) ) {
494 0 : txnm->reference_slot = blockhash->slot;
495 0 : if( FD_UNLIKELY( txnm->reference_slot+151UL<ctx->completed_slot ) ) {
496 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
497 0 : ctx->metrics.blockhash_expired++;
498 0 : return;
499 0 : }
500 0 : }
501 :
502 0 : int is_bundle_member = !!txnm->block_engine.bundle_id;
503 0 : int is_durable_nonce = fd_resolv_is_durable_nonce( txnt, fd_txn_m_payload( txnm ) );
504 :
505 0 : if( FD_UNLIKELY( !is_bundle_member && !is_durable_nonce && !blockhash ) ) {
506 0 : ulong pool_idx;
507 0 : if( FD_UNLIKELY( !pool_free( ctx->pool ) ) ) {
508 0 : pool_idx = lru_list_idx_pop_tail( ctx->lru_list, ctx->pool );
509 0 : map_chain_idx_remove_fast( ctx->map_chain, pool_idx, ctx->pool );
510 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_OVERRUN_IDX ]++;
511 0 : } else {
512 0 : pool_idx = pool_idx_acquire( ctx->pool );
513 0 : }
514 :
515 0 : fd_stashed_txn_m_t * stash_txn = pool_ele( ctx->pool, pool_idx );
516 : /* There's a compiler bug in GCC version 12 (at least 12.1, 12.3 and
517 : 12.4) that cause it to think stash_txn is a null pointer. It
518 : then complains that the memcpy is bad and refuses to compile the
519 : memcpy below. It is possible for pool_ele to return NULL, but
520 : that can't happen because if pool_free is 0, then all the pool
521 : elements must be in the LRU list, so idx_pop_tail won't return
522 : IDX_NULL; and if pool_free returns non-zero, then
523 : pool_idx_acquire won't return POOL_IDX_NULL. */
524 0 : FD_COMPILER_FORGET( stash_txn );
525 0 : fd_memcpy( stash_txn->_, txnm, fd_txn_m_realized_footprint( txnm, 1, 0 ) );
526 0 : stash_txn->blockhash = (blockhash_t *)(fd_txn_m_payload( (fd_txn_m_t *)(stash_txn->_) ) + txnt->recent_blockhash_off);
527 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_INSERTED_IDX ]++;
528 :
529 0 : map_chain_ele_insert( ctx->map_chain, stash_txn, ctx->pool );
530 0 : lru_list_idx_push_head( ctx->lru_list, pool_idx, ctx->pool );
531 :
532 0 : return;
533 0 : }
534 :
535 0 : if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) {
536 0 : if( FD_UNLIKELY( !ctx->bank->data ) ) {
537 0 : FD_MCNT_INC( RESOLF, NO_BANK_DROP, 1 );
538 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
539 0 : return;
540 0 : }
541 :
542 0 : int result = peek_aluts( ctx, txnm );
543 0 : if( FD_UNLIKELY( result ) ) {
544 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
545 0 : return;
546 0 : }
547 0 : }
548 :
549 0 : ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 );
550 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
551 0 : fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, tsorig, tspub );
552 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
553 0 : }
554 :
555 : static void
556 : unprivileged_init( fd_topo_t * topo,
557 0 : fd_topo_tile_t * tile ) {
558 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
559 :
560 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
561 0 : fd_resolv_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_resolv_ctx_t ), sizeof( fd_resolv_ctx_t ) );
562 :
563 0 : ctx->round_robin_cnt = fd_topo_tile_name_cnt( topo, tile->name );
564 0 : ctx->round_robin_idx = tile->kind_id;
565 :
566 0 : ctx->bundle_failed = 0;
567 0 : ctx->bundle_id = 0UL;
568 :
569 0 : ctx->completed_slot = 0UL;
570 0 : ctx->blockhash_ring_idx = 0UL;
571 :
572 0 : ctx->flush_pool_idx = ULONG_MAX;
573 :
574 0 : ctx->pool = pool_join( pool_new( FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint( 1UL<<16UL ) ), 1UL<<16UL ) );
575 0 : FD_TEST( ctx->pool );
576 :
577 0 : ctx->map_chain = map_chain_join( map_chain_new( FD_SCRATCH_ALLOC_APPEND( l, map_chain_align(), map_chain_footprint( 8192ULL ) ), 8192UL , 0UL ) );
578 0 : FD_TEST( ctx->map_chain );
579 :
580 0 : FD_TEST( ctx->lru_list==lru_list_join( lru_list_new( ctx->lru_list ) ) );
581 :
582 0 : memset( ctx->blockhash_ring, 0, sizeof( ctx->blockhash_ring ) );
583 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
584 :
585 0 : ctx->blockhash_map = map_join( map_new( FD_SCRATCH_ALLOC_APPEND( l, map_align(), map_footprint() ) ) );
586 0 : FD_TEST( ctx->blockhash_map );
587 :
588 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
589 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
590 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
591 0 : fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
592 :
593 0 : if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in[ i ].kind = IN_KIND_REPLAY;
594 0 : else if( FD_LIKELY( !strcmp( link->name, "dedup_resolv" ) ) ) ctx->in[ i ].kind = IN_KIND_DEDUP;
595 0 : else FD_LOG_ERR(( "unknown in link name '%s'", link->name ));
596 :
597 0 : ctx->in[i].mem = link_wksp->wksp;
598 0 : ctx->in[i].chunk0 = fd_dcache_compact_chunk0( ctx->in[i].mem, link->dcache );
599 0 : ctx->in[i].wmark = fd_dcache_compact_wmark ( ctx->in[i].mem, link->dcache, link->mtu );
600 0 : ctx->in[i].mtu = link->mtu;
601 0 : }
602 :
603 0 : ctx->out_pack->mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
604 0 : ctx->out_pack->chunk0 = fd_dcache_compact_chunk0( ctx->out_pack->mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
605 0 : ctx->out_pack->wmark = fd_dcache_compact_wmark ( ctx->out_pack->mem, topo->links[ tile->out_link_id[ 0 ] ].dcache, topo->links[ tile->out_link_id[ 0 ] ].mtu );
606 0 : ctx->out_pack->chunk = ctx->out_pack->chunk0;
607 :
608 0 : ctx->out_replay->mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 1 ] ].dcache_obj_id ].wksp_id ].wksp;
609 0 : ctx->out_replay->chunk0 = fd_dcache_compact_chunk0( ctx->out_replay->mem, topo->links[ tile->out_link_id[ 1 ] ].dcache );
610 0 : ctx->out_replay->wmark = fd_dcache_compact_wmark ( ctx->out_replay->mem, topo->links[ tile->out_link_id[ 1 ] ].dcache, topo->links[ tile->out_link_id[ 1 ] ].mtu );
611 0 : ctx->out_replay->chunk = ctx->out_replay->chunk0;
612 :
613 0 : fd_accdb_init_from_topo( ctx->accdb, topo, tile );
614 :
615 0 : ulong banks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks" );
616 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
617 0 : ulong banks_locks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks_locks" );
618 0 : FD_TEST( banks_locks_obj_id!=ULONG_MAX );
619 0 : FD_TEST( fd_banks_join( ctx->banks, fd_topo_obj_laddr( topo, banks_obj_id ), fd_topo_obj_laddr( topo, banks_locks_obj_id ) ) );
620 0 : ctx->bank->data = NULL;
621 :
622 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
623 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
624 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
625 0 : }
626 :
627 : static ulong
628 : populate_allowed_seccomp( fd_topo_t const * topo,
629 : fd_topo_tile_t const * tile,
630 : ulong out_cnt,
631 0 : struct sock_filter * out ) {
632 0 : (void)topo;
633 0 : (void)tile;
634 :
635 0 : populate_sock_filter_policy_fd_resolv_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
636 0 : return sock_filter_policy_fd_resolv_tile_instr_cnt;
637 0 : }
638 :
639 : static ulong
640 : populate_allowed_fds( fd_topo_t const * topo,
641 : fd_topo_tile_t const * tile,
642 : ulong out_fds_cnt,
643 0 : int * out_fds ) {
644 0 : (void)topo;
645 0 : (void)tile;
646 :
647 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
648 :
649 0 : ulong out_cnt = 0UL;
650 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
651 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
652 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
653 0 : return out_cnt;
654 0 : }
655 :
656 0 : #define STEM_BURST (1UL)
657 :
658 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_resolv_ctx_t
659 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_resolv_ctx_t)
660 :
661 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
662 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
663 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
664 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
665 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
666 :
667 : #include "../../disco/stem/fd_stem.c"
668 :
669 : fd_topo_run_tile_t fd_tile_resolv = {
670 : .name = "resolv",
671 : .populate_allowed_seccomp = populate_allowed_seccomp,
672 : .populate_allowed_fds = populate_allowed_fds,
673 : .scratch_align = scratch_align,
674 : .scratch_footprint = scratch_footprint,
675 : .unprivileged_init = unprivileged_init,
676 : .run = stem_run,
677 : };
|