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