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