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 "../../disco/metrics/fd_metrics.h"
7 : #include "../../flamenco/accdb/fd_accdb_sync.h"
8 : #include "../../flamenco/accdb/fd_accdb_impl_v1.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;
150 : fd_bank_t * bank;
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 : ulong db_race;
171 : } metrics;
172 :
173 : fd_resolv_in_ctx_t in[ 64UL ];
174 :
175 : fd_resolv_out_ctx_t out_pack[ 1UL ];
176 : fd_resolv_out_ctx_t out_replay[ 1UL ];
177 : } fd_resolv_ctx_t;
178 :
179 : FD_FN_CONST static inline ulong
180 0 : scratch_align( void ) {
181 0 : return alignof( fd_resolv_ctx_t );
182 0 : }
183 :
184 : FD_FN_PURE static inline ulong
185 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
186 0 : (void)tile;
187 0 : ulong l = FD_LAYOUT_INIT;
188 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_resolv_ctx_t ), sizeof( fd_resolv_ctx_t ) );
189 0 : l = FD_LAYOUT_APPEND( l, pool_align(), pool_footprint ( 1UL<<16UL ) );
190 0 : l = FD_LAYOUT_APPEND( l, map_chain_align(), map_chain_footprint( 8192UL ) );
191 0 : l = FD_LAYOUT_APPEND( l, map_align(), map_footprint() );
192 0 : return FD_LAYOUT_FINI( l, scratch_align() );
193 0 : }
194 :
195 : static inline void
196 0 : metrics_write( fd_resolv_ctx_t * ctx ) {
197 0 : FD_MCNT_SET( RESOLF, BLOCKHASH_EXPIRED, ctx->metrics.blockhash_expired );
198 0 : FD_MCNT_ENUM_COPY( RESOLF, LUT_RESOLVED, ctx->metrics.lut );
199 0 : FD_MCNT_ENUM_COPY( RESOLF, STASH_OPERATION, ctx->metrics.stash );
200 0 : FD_MCNT_SET( RESOLF, TRANSACTION_BUNDLE_PEER_FAILURE, ctx->metrics.bundle_peer_failure );
201 0 : FD_MCNT_SET( RESOLF, DB_RACES, ctx->metrics.db_race );
202 0 : }
203 :
204 : static int
205 : before_frag( fd_resolv_ctx_t * ctx,
206 : ulong in_idx,
207 : ulong seq,
208 0 : ulong sig ) {
209 0 : (void)sig;
210 :
211 0 : if( FD_UNLIKELY( ctx->in[in_idx].kind==IN_KIND_REPLAY ) ) return 0;
212 :
213 0 : return (seq % ctx->round_robin_cnt) != ctx->round_robin_idx;
214 0 : }
215 :
216 : static inline void
217 : during_frag( fd_resolv_ctx_t * ctx,
218 : ulong in_idx,
219 : ulong seq FD_PARAM_UNUSED,
220 : ulong sig FD_PARAM_UNUSED,
221 : ulong chunk,
222 : ulong sz,
223 0 : ulong ctl FD_PARAM_UNUSED ) {
224 :
225 0 : if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) )
226 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 ));
227 :
228 0 : switch( ctx->in[in_idx].kind ) {
229 0 : case IN_KIND_DEDUP: {
230 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
231 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
232 0 : fd_memcpy( dst, src, sz );
233 0 : break;
234 0 : }
235 0 : case IN_KIND_REPLAY: {
236 0 : if( FD_UNLIKELY( sig==REPLAY_SIG_ROOT_ADVANCED ) ) {
237 0 : ctx->_rooted_slot_msg = *(fd_replay_root_advanced_t *)fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk );
238 0 : } else if( FD_UNLIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) {
239 0 : ctx->_completed_slot_msg = *(fd_replay_slot_completed_t *)fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk );
240 0 : }
241 0 : break;
242 0 : }
243 0 : default:
244 0 : FD_LOG_ERR(( "unknown in kind %d", ctx->in[in_idx].kind ));
245 0 : }
246 0 : }
247 :
248 : /* peek_alut reads a single address lookup table from database cache. */
249 :
250 : static int
251 : peek_alut( fd_resolv_ctx_t * ctx,
252 : fd_txn_m_t * txnm,
253 : fd_alut_interp_t * interp,
254 0 : ulong alut_idx ) {
255 0 : fd_funk_txn_xid_t const xid = { .ul = { fd_bank_slot_get( ctx->bank ), fd_bank_slot_get( ctx->bank ) } };
256 :
257 0 : ulong ro_indir_cnt_old = interp->ro_indir_cnt;
258 0 : ulong rw_indir_cnt_old = interp->rw_indir_cnt;
259 0 : ulong alut_idx_old = interp->alut_idx;
260 :
261 0 : fd_txn_t const * txn = fd_txn_m_txn_t_const ( txnm );
262 0 : uchar const * txn_payload = fd_txn_m_payload_const( txnm );
263 0 : fd_txn_acct_addr_lut_t const * addr_lut =
264 0 : &fd_txn_get_address_tables_const( txn )[ alut_idx ];
265 0 : fd_pubkey_t addr_lut_acc = FD_LOAD( fd_pubkey_t, txn_payload+addr_lut->addr_off );
266 :
267 0 : int err = 0;
268 0 : for(;;) {
269 0 : fd_accdb_peek_t _peek[1];
270 0 : fd_accdb_peek_t * peek = fd_accdb_peek(
271 0 : ctx->accdb, _peek, &xid, &addr_lut_acc );
272 0 : if( FD_UNLIKELY( !peek ) ) {
273 0 : err = FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
274 0 : break;
275 0 : }
276 :
277 0 : err = fd_alut_interp_next(
278 0 : interp,
279 0 : &addr_lut_acc,
280 0 : fd_accdb_ref_owner ( peek->acc ),
281 0 : fd_accdb_ref_data_const( peek->acc ),
282 0 : fd_accdb_ref_data_sz ( peek->acc ) );
283 :
284 0 : int peek_ok = fd_accdb_peek_test( peek );
285 0 : fd_accdb_peek_drop( peek );
286 0 : if( FD_LIKELY( peek_ok ) ) break;
287 :
288 : /* Restore old interp state and retry */
289 0 : FD_SPIN_PAUSE();
290 0 : ctx->metrics.db_race++;
291 0 : interp->ro_indir_cnt = ro_indir_cnt_old;
292 0 : interp->rw_indir_cnt = rw_indir_cnt_old;
293 0 : interp->alut_idx = alut_idx_old;
294 0 : }
295 :
296 0 : return err;
297 0 : }
298 :
299 : /* peek_aluts reads address lookup tables from database cache.
300 : Gracefully recovers from data races and missing accounts. */
301 :
302 : static int
303 : peek_aluts( fd_resolv_ctx_t * ctx,
304 0 : fd_txn_m_t * txnm ) {
305 :
306 : /* Unpack context */
307 0 : fd_txn_t const * txn = fd_txn_m_txn_t_const ( txnm );
308 0 : uchar const * txn_payload = fd_txn_m_payload_const( txnm );
309 0 : ulong const alut_cnt = txn->addr_table_lookup_cnt;
310 0 : ulong const slot = fd_bank_slot_get( ctx->bank );
311 0 : fd_sysvar_cache_t const * sysvar_cache = fd_bank_sysvar_cache_query( ctx->bank ); FD_TEST( sysvar_cache );
312 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( sysvar_cache );
313 :
314 : /* Write indirect addrs into here */
315 0 : fd_acct_addr_t * indir_addrs = fd_txn_m_alut( txnm );
316 :
317 0 : int err = FD_RUNTIME_EXECUTE_SUCCESS;
318 0 : fd_alut_interp_t interp[1];
319 0 : fd_alut_interp_new( interp, indir_addrs, txn, txn_payload, slot_hashes, slot );
320 0 : for( ulong i=0UL; i<alut_cnt; i++ ) {
321 0 : err = peek_alut( ctx, txnm, interp, i );
322 0 : if( FD_UNLIKELY( err ) ) break;
323 0 : }
324 0 : fd_alut_interp_delete( interp );
325 0 : fd_sysvar_cache_slot_hashes_leave_const( sysvar_cache, slot_hashes );
326 :
327 0 : ulong ctr_idx;
328 0 : switch( err ) {
329 0 : case FD_RUNTIME_EXECUTE_SUCCESS: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_SUCCESS_IDX; break;
330 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;
331 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;
332 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;
333 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;
334 0 : default: ctr_idx = FD_METRICS_ENUM_LUT_RESOLVE_RESULT_V_ACCOUNT_UNINITIALIZED_IDX; break;
335 0 : }
336 0 : ctx->metrics.lut[ ctr_idx ]++;
337 0 : return err;
338 0 : }
339 :
340 : static int
341 : publish_txn( fd_resolv_ctx_t * ctx,
342 : fd_stem_context_t * stem,
343 0 : fd_stashed_txn_m_t const * stashed ) {
344 0 : fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
345 0 : fd_memcpy( txnm, stashed->_, fd_txn_m_realized_footprint( (fd_txn_m_t *)stashed->_, 1, 0 ) );
346 :
347 0 : fd_txn_t const * txnt = fd_txn_m_txn_t( txnm );
348 :
349 0 : txnm->reference_slot = ctx->flushing_slot;
350 :
351 0 : if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) {
352 0 : if( FD_UNLIKELY( !ctx->bank ) ) {
353 0 : FD_MCNT_INC( RESOLF, NO_BANK_DROP, 1 );
354 0 : return 0;
355 0 : }
356 0 : int err = peek_aluts( ctx, txnm );
357 0 : if( FD_UNLIKELY( err ) ) return 0;
358 0 : }
359 :
360 0 : ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 );
361 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
362 0 : fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, 0UL, tspub );
363 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
364 :
365 0 : return 1;
366 0 : }
367 :
368 : static inline void
369 : after_credit( fd_resolv_ctx_t * ctx,
370 : fd_stem_context_t * stem,
371 : int * opt_poll_in,
372 0 : int * charge_busy ) {
373 0 : if( FD_LIKELY( ctx->flush_pool_idx==ULONG_MAX ) ) return;
374 :
375 0 : *charge_busy = 1;
376 0 : *opt_poll_in = 0;
377 :
378 0 : ulong next = map_chain_idx_next_const( ctx->flush_pool_idx, ULONG_MAX, ctx->pool );
379 0 : map_chain_idx_remove_fast( ctx->map_chain, ctx->flush_pool_idx, ctx->pool );
380 0 : if( FD_LIKELY( publish_txn( ctx, stem, pool_ele( ctx->pool, ctx->flush_pool_idx ) ) ) ) {
381 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_PUBLISHED_IDX ]++;
382 0 : } else {
383 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_REMOVED_IDX ]++;
384 0 : }
385 0 : lru_list_idx_remove( ctx->lru_list, ctx->flush_pool_idx, ctx->pool );
386 0 : pool_idx_release( ctx->pool, ctx->flush_pool_idx );
387 0 : ctx->flush_pool_idx = next;
388 0 : }
389 :
390 : /* Returns 0 if not a durable nonce transaction and 1 if it may be a
391 : durable nonce transaction */
392 :
393 : FD_FN_PURE static inline int
394 : fd_resolv_is_durable_nonce( fd_txn_t const * txn,
395 0 : uchar const * payload ) {
396 0 : if( FD_UNLIKELY( txn->instr_cnt==0 ) ) return 0;
397 :
398 0 : fd_txn_instr_t const * ix0 = &txn->instr[ 0 ];
399 0 : fd_acct_addr_t const * prog0 = fd_txn_get_acct_addrs( txn, payload ) + ix0->program_id;
400 : /* First instruction must be SystemProgram nonceAdvance instruction */
401 0 : fd_acct_addr_t const system_program[1] = { { { SYS_PROG_ID } } };
402 0 : if( FD_LIKELY( memcmp( prog0, system_program, sizeof(fd_acct_addr_t) ) ) ) return 0;
403 :
404 : /* instruction with three accounts and a four byte instruction data, a
405 : little-endian uint value 4 */
406 0 : if( FD_UNLIKELY( (ix0->data_sz!=4) | (ix0->acct_cnt!=3) ) ) return 0;
407 :
408 0 : return fd_uint_load_4( payload + ix0->data_off )==4U;
409 0 : }
410 :
411 : static inline void
412 : after_frag( fd_resolv_ctx_t * ctx,
413 : ulong in_idx,
414 : ulong seq,
415 : ulong sig,
416 : ulong sz,
417 : ulong tsorig,
418 : ulong _tspub,
419 0 : fd_stem_context_t * stem ) {
420 0 : (void)seq;
421 0 : (void)sz;
422 0 : (void)_tspub;
423 :
424 0 : if( FD_UNLIKELY( ctx->in[in_idx].kind==IN_KIND_REPLAY ) ) {
425 0 : switch( sig ) {
426 0 : case REPLAY_SIG_SLOT_COMPLETED: {
427 0 : fd_replay_slot_completed_t const * msg = &ctx->_completed_slot_msg;
428 :
429 : /* blockhash_ring is initalized to all zeros. blockhash=0 is an illegal map query */
430 0 : if( FD_UNLIKELY( memcmp( &ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], (uchar[ 32UL ]){ 0UL }, sizeof(blockhash_t) ) ) ) {
431 0 : blockhash_map_t * entry = map_query( ctx->blockhash_map, ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], NULL );
432 0 : if( FD_LIKELY( entry ) ) map_remove( ctx->blockhash_map, entry );
433 0 : }
434 :
435 0 : memcpy( ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ].b, msg->block_hash.uc, 32UL );
436 0 : ctx->blockhash_ring_idx++;
437 :
438 0 : blockhash_map_t * blockhash = map_insert( ctx->blockhash_map, *(blockhash_t *)msg->block_hash.uc );
439 0 : blockhash->slot = msg->slot;
440 :
441 0 : blockhash_t * hash = (blockhash_t *)msg->block_hash.uc;
442 0 : ctx->flush_pool_idx = map_chain_idx_query_const( ctx->map_chain, &hash, ULONG_MAX, ctx->pool );
443 0 : ctx->flushing_slot = msg->slot;
444 :
445 0 : ctx->completed_slot = msg->slot;
446 0 : break;
447 0 : }
448 0 : case REPLAY_SIG_ROOT_ADVANCED: {
449 0 : fd_replay_root_advanced_t const * msg = &ctx->_rooted_slot_msg;
450 :
451 : /* Replace current bank with new bank */
452 0 : fd_bank_t * prev_bank = ctx->bank;
453 :
454 0 : ctx->bank = fd_banks_bank_query( ctx->banks, msg->bank_idx );
455 0 : FD_TEST( ctx->bank );
456 :
457 : /* Send slot completed message back to replay, so it can
458 : decrement the reference count of the previous bank. */
459 0 : if( FD_LIKELY( prev_bank ) ) {
460 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
461 0 : fd_resolv_slot_exchanged_t * slot_exchanged =
462 0 : fd_type_pun( fd_chunk_to_laddr( ctx->out_replay->mem, ctx->out_replay->chunk ) );
463 0 : slot_exchanged->bank_idx = prev_bank->idx;
464 0 : fd_stem_publish( stem, 1UL, 0UL, ctx->out_replay->chunk, sizeof(fd_resolv_slot_exchanged_t), 0UL, tsorig, tspub );
465 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 );
466 0 : }
467 :
468 0 : break;
469 0 : }
470 0 : default: break;
471 0 : }
472 0 : return;
473 0 : }
474 :
475 0 : fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk );
476 0 : FD_TEST( txnm->payload_sz<=FD_TPU_MTU );
477 0 : FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ );
478 0 : fd_txn_t const * txnt = fd_txn_m_txn_t( txnm );
479 :
480 : /* If we find the recent blockhash, life is simple. We drop
481 : transactions that couldn't possibly execute any more, and forward
482 : to pack ones that could.
483 :
484 : If we can't find the recent blockhash ... it means one of four
485 : things,
486 :
487 : (1) The blockhash is really old (more than 19 days) or just
488 : non-existent.
489 : (2) The blockhash is not that old, but was created before this
490 : validator was started.
491 : (3) It's really new (we haven't seen the bank yet).
492 : (4) It's a durable nonce transaction, or part of a bundle (just let
493 : it pass).
494 :
495 : For durable nonce transactions, there isn't much we can do except
496 : pass them along and see if they execute.
497 :
498 : For the other three cases ... we don't want to flood pack with what
499 : might be junk transactions, so we accumulate them into a local
500 : buffer. If we later see the blockhash come to exist, we forward any
501 : buffered transactions to back. */
502 :
503 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id && (txnm->block_engine.bundle_id!=ctx->bundle_id) ) ) {
504 0 : ctx->bundle_failed = 0;
505 0 : ctx->bundle_id = txnm->block_engine.bundle_id;
506 0 : }
507 :
508 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id && ctx->bundle_failed ) ) {
509 0 : ctx->metrics.bundle_peer_failure++;
510 0 : return;
511 0 : }
512 :
513 0 : txnm->reference_slot = ctx->completed_slot;
514 0 : blockhash_map_t const * blockhash = map_query_const( ctx->blockhash_map, *(blockhash_t*)( fd_txn_m_payload( txnm )+txnt->recent_blockhash_off ), NULL );
515 0 : if( FD_LIKELY( blockhash ) ) {
516 0 : txnm->reference_slot = blockhash->slot;
517 0 : if( FD_UNLIKELY( txnm->reference_slot+151UL<ctx->completed_slot ) ) {
518 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
519 0 : ctx->metrics.blockhash_expired++;
520 0 : return;
521 0 : }
522 0 : }
523 :
524 0 : int is_bundle_member = !!txnm->block_engine.bundle_id;
525 0 : int is_durable_nonce = fd_resolv_is_durable_nonce( txnt, fd_txn_m_payload( txnm ) );
526 :
527 0 : if( FD_UNLIKELY( !is_bundle_member && !is_durable_nonce && !blockhash ) ) {
528 0 : ulong pool_idx;
529 0 : if( FD_UNLIKELY( !pool_free( ctx->pool ) ) ) {
530 0 : pool_idx = lru_list_idx_pop_tail( ctx->lru_list, ctx->pool );
531 0 : map_chain_idx_remove_fast( ctx->map_chain, pool_idx, ctx->pool );
532 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_OVERRUN_IDX ]++;
533 0 : } else {
534 0 : pool_idx = pool_idx_acquire( ctx->pool );
535 0 : }
536 :
537 0 : fd_stashed_txn_m_t * stash_txn = pool_ele( ctx->pool, pool_idx );
538 : /* There's a compiler bug in GCC version 12 (at least 12.1, 12.3 and
539 : 12.4) that cause it to think stash_txn is a null pointer. It
540 : then complains that the memcpy is bad and refuses to compile the
541 : memcpy below. It is possible for pool_ele to return NULL, but
542 : that can't happen because if pool_free is 0, then all the pool
543 : elements must be in the LRU list, so idx_pop_tail won't return
544 : IDX_NULL; and if pool_free returns non-zero, then
545 : pool_idx_acquire won't return POOL_IDX_NULL. */
546 0 : FD_COMPILER_FORGET( stash_txn );
547 0 : fd_memcpy( stash_txn->_, txnm, fd_txn_m_realized_footprint( txnm, 1, 0 ) );
548 0 : stash_txn->blockhash = (blockhash_t *)(fd_txn_m_payload( (fd_txn_m_t *)(stash_txn->_) ) + txnt->recent_blockhash_off);
549 0 : ctx->metrics.stash[ FD_METRICS_ENUM_RESOLVE_STASH_OPERATION_V_INSERTED_IDX ]++;
550 :
551 0 : map_chain_ele_insert( ctx->map_chain, stash_txn, ctx->pool );
552 0 : lru_list_idx_push_head( ctx->lru_list, pool_idx, ctx->pool );
553 :
554 0 : return;
555 0 : }
556 :
557 0 : if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) {
558 0 : if( FD_UNLIKELY( !ctx->bank ) ) {
559 0 : FD_MCNT_INC( RESOLF, NO_BANK_DROP, 1 );
560 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
561 0 : return;
562 0 : }
563 :
564 0 : int result = peek_aluts( ctx, txnm );
565 0 : if( FD_UNLIKELY( result ) ) {
566 0 : if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1;
567 0 : return;
568 0 : }
569 0 : }
570 :
571 0 : ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 );
572 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
573 0 : fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, tsorig, tspub );
574 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
575 0 : }
576 :
577 : static void
578 : unprivileged_init( fd_topo_t * topo,
579 0 : fd_topo_tile_t * tile ) {
580 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
581 :
582 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
583 0 : fd_resolv_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_resolv_ctx_t ), sizeof( fd_resolv_ctx_t ) );
584 :
585 0 : ctx->round_robin_cnt = fd_topo_tile_name_cnt( topo, tile->name );
586 0 : ctx->round_robin_idx = tile->kind_id;
587 :
588 0 : ctx->bundle_failed = 0;
589 0 : ctx->bundle_id = 0UL;
590 :
591 0 : ctx->completed_slot = 0UL;
592 0 : ctx->blockhash_ring_idx = 0UL;
593 :
594 0 : ctx->flush_pool_idx = ULONG_MAX;
595 :
596 0 : ctx->pool = pool_join( pool_new( FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint( 1UL<<16UL ) ), 1UL<<16UL ) );
597 0 : FD_TEST( ctx->pool );
598 :
599 0 : ctx->map_chain = map_chain_join( map_chain_new( FD_SCRATCH_ALLOC_APPEND( l, map_chain_align(), map_chain_footprint( 8192ULL ) ), 8192UL , 0UL ) );
600 0 : FD_TEST( ctx->map_chain );
601 :
602 0 : FD_TEST( ctx->lru_list==lru_list_join( lru_list_new( ctx->lru_list ) ) );
603 :
604 0 : memset( ctx->blockhash_ring, 0, sizeof( ctx->blockhash_ring ) );
605 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
606 :
607 0 : ctx->blockhash_map = map_join( map_new( FD_SCRATCH_ALLOC_APPEND( l, map_align(), map_footprint() ) ) );
608 0 : FD_TEST( ctx->blockhash_map );
609 :
610 0 : FD_TEST( tile->in_cnt<=sizeof( ctx->in )/sizeof( ctx->in[ 0 ] ) );
611 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
612 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
613 0 : fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
614 :
615 0 : if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in[ i ].kind = IN_KIND_REPLAY;
616 0 : else if( FD_LIKELY( !strcmp( link->name, "dedup_resolv" ) ) ) ctx->in[ i ].kind = IN_KIND_DEDUP;
617 0 : else FD_LOG_ERR(( "unknown in link name '%s'", link->name ));
618 :
619 0 : ctx->in[i].mem = link_wksp->wksp;
620 0 : ctx->in[i].chunk0 = fd_dcache_compact_chunk0( ctx->in[i].mem, link->dcache );
621 0 : ctx->in[i].wmark = fd_dcache_compact_wmark ( ctx->in[i].mem, link->dcache, link->mtu );
622 0 : ctx->in[i].mtu = link->mtu;
623 0 : }
624 :
625 0 : ctx->out_pack->mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
626 0 : ctx->out_pack->chunk0 = fd_dcache_compact_chunk0( ctx->out_pack->mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
627 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 );
628 0 : ctx->out_pack->chunk = ctx->out_pack->chunk0;
629 :
630 0 : ctx->out_replay->mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 1 ] ].dcache_obj_id ].wksp_id ].wksp;
631 0 : ctx->out_replay->chunk0 = fd_dcache_compact_chunk0( ctx->out_replay->mem, topo->links[ tile->out_link_id[ 1 ] ].dcache );
632 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 );
633 0 : ctx->out_replay->chunk = ctx->out_replay->chunk0;
634 :
635 0 : FD_TEST( fd_accdb_user_v1_init( ctx->accdb, fd_topo_obj_laddr( topo, tile->resolv.funk_obj_id ) ) );
636 :
637 0 : ulong banks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks" );
638 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
639 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
640 0 : FD_TEST( ctx->banks );
641 0 : ctx->bank = NULL;
642 :
643 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
644 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
645 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
646 0 : }
647 :
648 : static ulong
649 : populate_allowed_seccomp( fd_topo_t const * topo,
650 : fd_topo_tile_t const * tile,
651 : ulong out_cnt,
652 0 : struct sock_filter * out ) {
653 0 : (void)topo;
654 0 : (void)tile;
655 :
656 0 : populate_sock_filter_policy_fd_resolv_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
657 0 : return sock_filter_policy_fd_resolv_tile_instr_cnt;
658 0 : }
659 :
660 : static ulong
661 : populate_allowed_fds( fd_topo_t const * topo,
662 : fd_topo_tile_t const * tile,
663 : ulong out_fds_cnt,
664 0 : int * out_fds ) {
665 0 : (void)topo;
666 0 : (void)tile;
667 :
668 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
669 :
670 0 : ulong out_cnt = 0UL;
671 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
672 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
673 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
674 0 : return out_cnt;
675 0 : }
676 :
677 0 : #define STEM_BURST (1UL)
678 :
679 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_resolv_ctx_t
680 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_resolv_ctx_t)
681 :
682 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
683 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
684 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
685 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
686 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
687 :
688 : #include "../../disco/stem/fd_stem.c"
689 :
690 : fd_topo_run_tile_t fd_tile_resolv = {
691 : .name = "resolv",
692 : .populate_allowed_seccomp = populate_allowed_seccomp,
693 : .populate_allowed_fds = populate_allowed_fds,
694 : .scratch_align = scratch_align,
695 : .scratch_footprint = scratch_footprint,
696 : .unprivileged_init = unprivileged_init,
697 : .run = stem_run,
698 : };
|