Line data Source code
1 : #include "fd_execle_err.h"
2 :
3 : #include "../../disco/tiles.h"
4 : #include "generated/fd_execle_tile_seccomp.h"
5 : #include "../../disco/pack/fd_pack.h"
6 : #include "../../disco/pack/fd_pack_cost.h"
7 : #include "../../ballet/blake3/fd_blake3.h"
8 : #include "../../ballet/bmtree/fd_bmtree.h"
9 : #include "../../disco/metrics/fd_metrics.h"
10 : #include "../../util/pod/fd_pod_format.h"
11 : #include "../../disco/pack/fd_pack_rebate_sum.h"
12 : #include "../../disco/metrics/generated/fd_metrics_enums.h"
13 : #include "../../discof/fd_accdb_topo.h"
14 : #include "../../flamenco/runtime/fd_runtime.h"
15 : #include "../../flamenco/runtime/fd_bank.h"
16 : #include "../../flamenco/runtime/fd_acc_pool.h"
17 : #include "../../flamenco/progcache/fd_progcache_user.h"
18 : #include "../../flamenco/log_collector/fd_log_collector_base.h"
19 :
20 : struct fd_execle_out {
21 : ulong idx;
22 : fd_wksp_t * mem;
23 : ulong chunk0;
24 : ulong wmark;
25 : ulong chunk;
26 : };
27 :
28 : typedef struct fd_execle_out fd_execle_out_t;
29 :
30 : struct fd_execle_tile {
31 : ulong kind_id;
32 :
33 : fd_blake3_t * blake3;
34 : void * bmtree;
35 :
36 : ulong _bank_idx;
37 : ulong _pack_idx;
38 : ulong _txn_idx;
39 : int _is_bundle;
40 : fd_acct_addr_t _alt_accts[MAX_TXN_PER_MICROBLOCK][FD_TXN_ACCT_ADDR_MAX];
41 :
42 : ulong * busy_fseq;
43 :
44 : fd_wksp_t * pack_in_mem;
45 : ulong pack_in_chunk0;
46 : ulong pack_in_wmark;
47 :
48 : fd_execle_out_t out_poh[1];
49 : fd_execle_out_t out_pack[1];
50 :
51 : ulong rebates_for_slot;
52 : int enable_rebates;
53 : fd_pack_rebate_sum_t rebater[ 1 ];
54 :
55 : fd_banks_t banks[1];
56 :
57 : fd_accdb_user_t accdb[1];
58 : fd_progcache_t progcache[1];
59 :
60 : fd_runtime_t runtime[1];
61 :
62 : /* For bundle execution, we need to execute each transaction against
63 : a separate transaction context and a set of accounts, but the exec
64 : stack can be reused. We will also use these same memory regions
65 : for non-bundle execution. */
66 : fd_txn_in_t txn_in[ FD_PACK_MAX_TXN_PER_BUNDLE ];
67 : fd_txn_out_t txn_out[ FD_PACK_MAX_TXN_PER_BUNDLE ];
68 :
69 : fd_log_collector_t log_collector[ 1 ];
70 :
71 : struct {
72 : ulong txn_result[ FD_METRICS_ENUM_TRANSACTION_RESULT_CNT ];
73 : ulong txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_CNT ];
74 : } metrics;
75 : };
76 :
77 : typedef struct fd_execle_tile fd_execle_tile_t;
78 :
79 : FD_FN_CONST static inline ulong
80 0 : scratch_align( void ) {
81 0 : return 128UL;
82 0 : }
83 :
84 : FD_FN_PURE static inline ulong
85 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
86 0 : ulong l = FD_LAYOUT_INIT;
87 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_execle_tile_t ), sizeof( fd_execle_tile_t ) );
88 0 : l = FD_LAYOUT_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
89 0 : l = FD_LAYOUT_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
90 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->execle.max_live_slots ) );
91 0 : l = FD_LAYOUT_APPEND( l, FD_PROGCACHE_SCRATCH_ALIGN, FD_PROGCACHE_SCRATCH_FOOTPRINT );
92 0 : return FD_LAYOUT_FINI( l, scratch_align() );
93 0 : }
94 :
95 : static inline void
96 0 : metrics_write( fd_execle_tile_t * ctx ) {
97 0 : FD_MCNT_ENUM_COPY( EXECLE, TRANSACTION_RESULT, ctx->metrics.txn_result );
98 0 : FD_MCNT_ENUM_COPY( EXECLE, TRANSACTION_LANDED, ctx->metrics.txn_landed );
99 :
100 0 : FD_MCNT_SET( EXECLE, COMPUTE_UNITS_TOTAL, ctx->runtime->metrics.cu_cum );
101 0 : }
102 :
103 : static int
104 : before_frag( fd_execle_tile_t * ctx,
105 : ulong in_idx,
106 : ulong seq,
107 0 : ulong sig ) {
108 0 : (void)in_idx;
109 0 : (void)seq;
110 :
111 : /* Pack also outputs "leader slot done" which we can ignore. */
112 0 : if( FD_UNLIKELY( fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_MICROBLOCK ) ) return 1;
113 :
114 0 : ulong target_execle_kind_id = fd_disco_poh_sig_execle_tile( sig );
115 0 : if( FD_UNLIKELY( target_execle_kind_id!=ctx->kind_id ) ) return 1;
116 :
117 0 : return 0;
118 0 : }
119 :
120 : static inline void
121 : during_frag( fd_execle_tile_t * ctx,
122 : ulong in_idx,
123 : ulong seq,
124 : ulong sig,
125 : ulong chunk,
126 : ulong sz,
127 0 : ulong ctl ) {
128 0 : (void)in_idx; (void)seq; (void)sig; (void)ctl;
129 :
130 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->pack_in_mem, chunk );
131 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
132 :
133 0 : if( FD_UNLIKELY( chunk<ctx->pack_in_chunk0 || chunk>ctx->pack_in_wmark || sz>USHORT_MAX ) )
134 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->pack_in_chunk0, ctx->pack_in_wmark ));
135 :
136 : /* Pack sends fd_txn_e_t (with ALT accounts), but PoH expects fd_txn_p_t.
137 : We copy the fd_txn_p_t portion to the PoH output buffer, and copy the
138 : ALT accounts to the tile context for rebates. */
139 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
140 0 : fd_txn_e_t const * src_txn_e = (fd_txn_e_t const *)src;
141 0 : fd_txn_p_t * dst_txn_p = (fd_txn_p_t *)dst;
142 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
143 0 : fd_memcpy( dst_txn_p + i, src_txn_e[i].txnp, sizeof(fd_txn_p_t) );
144 0 : ulong alt_cnt = fd_ulong_min( (ulong)TXN(src_txn_e[i].txnp)->addr_table_adtl_cnt, FD_TXN_ACCT_ADDR_MAX );
145 0 : fd_memcpy( ctx->_alt_accts[i], src_txn_e[i].alt_accts, alt_cnt * sizeof(fd_acct_addr_t) );
146 0 : }
147 :
148 0 : fd_microblock_execle_trailer_t * trailer = (fd_microblock_execle_trailer_t *)( src+sz-sizeof(fd_microblock_execle_trailer_t) );
149 0 : ctx->_bank_idx = trailer->bank_idx;
150 0 : ctx->_pack_idx = trailer->pack_idx;
151 0 : ctx->_txn_idx = trailer->pack_txn_idx;
152 0 : ctx->_is_bundle = trailer->is_bundle;
153 0 : }
154 :
155 : static void
156 : hash_transactions( void * mem,
157 : fd_txn_p_t * txns,
158 : ulong txn_cnt,
159 0 : uchar * mixin ) {
160 0 : fd_bmtree_commit_t * bmtree = fd_bmtree_commit_init( mem, 32UL, 1UL, 0UL );
161 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
162 0 : fd_txn_p_t * _txn = txns + i;
163 0 : if( FD_UNLIKELY( !(_txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
164 :
165 0 : fd_txn_t * txn = TXN(_txn);
166 0 : for( ulong j=0; j<txn->signature_cnt; j++ ) {
167 0 : fd_bmtree_node_t node[1];
168 0 : fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+64UL*j, 64UL, 1UL );
169 0 : fd_bmtree_commit_append( bmtree, node, 1UL );
170 0 : }
171 0 : }
172 0 : uchar * root = fd_bmtree_commit_fini( bmtree );
173 0 : fd_memcpy( mixin, root, 32UL );
174 0 : }
175 :
176 : static inline void
177 : handle_microblock( fd_execle_tile_t * ctx,
178 : ulong seq,
179 : ulong sig,
180 : ulong sz,
181 : ulong begin_tspub,
182 0 : fd_stem_context_t * stem ) {
183 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
184 :
185 0 : ulong slot = fd_disco_poh_sig_slot( sig );
186 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
187 :
188 0 : fd_bank_t bank[1];
189 0 : FD_TEST( fd_banks_bank_query( bank, ctx->banks, ctx->_bank_idx ) );
190 0 : ulong bank_slot = fd_bank_slot_get( bank );
191 0 : FD_TEST( bank_slot==slot );
192 :
193 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
194 0 : fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );
195 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ 0 ];
196 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ 0 ];
197 :
198 0 : uint requested_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
199 0 : uint non_execution_cus = txn->pack_cu.non_execution_cus;
200 :
201 : /* Assume failure, set below if success. If it doesn't land in the
202 : block, rebate the non-execution CUs too. */
203 0 : txn->execle_cu.actual_consumed_cus = 0U;
204 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
205 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
206 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
207 :
208 0 : txn_in->bundle.is_bundle = 0;
209 0 : txn_in->txn = txn;
210 :
211 0 : fd_runtime_prepare_and_execute_txn( ctx->runtime, bank, txn_in, txn_out );
212 :
213 : /* Stash the result in the flags value so that pack can inspect it. */
214 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
215 :
216 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
217 0 : FD_TEST( !txn_out->err.is_fees_only );
218 0 : fd_runtime_cancel_txn( ctx->runtime, txn_out );
219 : /* Use pre-resolved ALT accounts for rebates even for unlanded transactions */
220 0 : fd_acct_addr_t const * writable_alt = ctx->_alt_accts[i];
221 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txn, &writable_alt, 1UL );
222 0 : ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_UNLANDED_IDX ]++;
223 0 : ctx->metrics.txn_result[ fd_execle_err_from_runtime_err( txn_out->err.txn_err ) ]++;
224 0 : continue;
225 0 : }
226 :
227 0 : if( FD_UNLIKELY( txn_out->err.is_fees_only ) ) ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_FEES_ONLY_IDX ]++;
228 0 : else if( FD_UNLIKELY( txn_out->err.txn_err ) ) ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_FAILED_IDX ]++;
229 0 : else ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_SUCCESS_IDX ]++;
230 :
231 : /* TXN_P_FLAGS_EXECUTE_SUCCESS means that it should be included in
232 : the block. It's a bit of a misnomer now that there are fee-only
233 : transactions. */
234 0 : txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS | FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
235 0 : ctx->metrics.txn_result[ fd_execle_err_from_runtime_err( txn_out->err.txn_err ) ]++;
236 :
237 : /* Commit must succeed so no failure path. Once commit is called,
238 : the transactions MUST be mixed into the PoH otherwise we will
239 : fork and diverge, so the link from here til PoH mixin must be
240 : completely reliable with nothing dropped.
241 :
242 : fd_runtime_commit_txn checks if the transaction fits into the
243 : block with the cost tracker. If it doesn't fit, flags is set to
244 : zero. A key invariant of the leader pipeline is that pack
245 : ensures all transactions must fit already, so it is a fatal error
246 : if that happens. We cannot reject the transaction here as there
247 : would be no way to undo the partially applied changes to the bank
248 : in finalize anyway. */
249 0 : fd_runtime_commit_txn( ctx->runtime, bank, txn_out );
250 :
251 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
252 : /* If the transaction failed to fit into the block, we need to
253 : updated the transaction flag with the error code. */
254 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
255 0 : fd_cost_tracker_t * cost_tracker = fd_bank_cost_tracker_locking_modify( bank );
256 0 : uchar * signature = (uchar *)txn_in->txn->payload + TXN( txn_in->txn )->signature_off;
257 0 : int err = fd_cost_tracker_try_add_cost( cost_tracker, txn_out );
258 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
259 0 : FD_BASE58_ENCODE_64_BYTES( signature, signature_b58 );
260 0 : FD_LOG_CRIT(( "transaction %s failed to fit into block despite pack guaranteeing it would "
261 0 : "(res=%d) [block_cost=%lu, vote_cost=%lu, allocated_accounts_data_size=%lu, "
262 0 : "block_cost_limit=%lu, vote_cost_limit=%lu, account_cost_limit=%lu]",
263 0 : signature_b58, err, cost_tracker->block_cost, cost_tracker->vote_cost,
264 0 : cost_tracker->allocated_accounts_data_size,
265 0 : cost_tracker->block_cost_limit, cost_tracker->vote_cost_limit,
266 0 : cost_tracker->account_cost_limit ));
267 0 : }
268 :
269 0 : uint actual_execution_cus = (uint)(txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter);
270 0 : uint actual_acct_data_cus = (uint)(txn_out->details.txn_cost.transaction.loaded_accounts_data_size_cost);
271 :
272 0 : int is_simple_vote = 0;
273 0 : if( FD_UNLIKELY( is_simple_vote = fd_txn_is_simple_vote_transaction( TXN(txn), txn->payload ) ) ) {
274 0 : if( !FD_FEATURE_ACTIVE_BANK( bank, remove_simple_vote_from_cost_model ) ) {
275 : /* TODO: remove this once remove_simple_vote_from_cost_model is
276 : activated */
277 :
278 : /* Simple votes are charged fixed amounts of compute regardless of
279 : the real cost they incur. Unclear what cost is returned by
280 : fd_execute txn, however, so we override it here. */
281 0 : actual_execution_cus = FD_PACK_VOTE_DEFAULT_COMPUTE_UNITS;
282 0 : actual_acct_data_cus = 0U;
283 0 : }
284 0 : }
285 :
286 : /* FeesOnly transactions are transactions that failed to load
287 : before they even reach the VM stage. They have zero execution
288 : cost but do charge for the account data they are able to load.
289 : FeesOnly votes are charged the fixed voe cost. */
290 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - (actual_execution_cus + actual_acct_data_cus);
291 0 : txn->execle_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus;
292 :
293 : /* Use ALT accounts copied in during_frag for rebates.
294 : These were resolved by resolv_tile and are needed because the LUT
295 : may be deactivated by the time we get here. */
296 0 : fd_acct_addr_t const * writable_alt = ctx->_alt_accts[i];
297 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txn, &writable_alt, 1UL );
298 :
299 : /* The VM will stop executing and fail an instruction immediately if
300 : it exceeds its requested CUs. A transaction which requests less
301 : account data than it actually consumes will fail in the account
302 : loading stage. */
303 0 : if( FD_UNLIKELY( actual_execution_cus+actual_acct_data_cus>requested_exec_plus_acct_data_cus ) ) {
304 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
305 0 : FD_LOG_ERR(( "Actual CUs unexpectedly exceeded requested amount. actual_execution_cus (%u) actual_acct_data_cus "
306 0 : "(%u) requested_exec_plus_acct_data_cus (%u) is_simple_vote (%i) exec_failed (%i)",
307 0 : actual_execution_cus, actual_acct_data_cus, requested_exec_plus_acct_data_cus, is_simple_vote,
308 0 : txn_out->err.txn_err ));
309 0 : }
310 :
311 0 : }
312 :
313 : /* Indicate to pack tile we are done processing the transactions so
314 : it can pack new microblocks using these accounts. */
315 0 : fd_fseq_update( ctx->busy_fseq, seq );
316 :
317 : /* Now produce the merkle hash of the transactions for inclusion
318 : (mixin) to the PoH hash. This is done on the execle tile because
319 : it shards / scales horizontally here, while PoH does not. */
320 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst + txn_cnt*sizeof(fd_txn_p_t) );
321 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, txn_cnt, trailer->hash );
322 0 : trailer->pack_txn_idx = ctx->_txn_idx;
323 0 : trailer->tips = ctx->txn_out[ 0 ].details.tips;
324 :
325 0 : long tickcount = fd_tickcount();
326 0 : long microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, tickcount );
327 0 : long microblock_duration_ticks = fd_long_max(tickcount - microblock_start_ticks, 0L);
328 :
329 0 : long tx_preload_end_ticks = fd_long_if( ctx->txn_out[ 0 ].details.prep_start_timestamp!=LONG_MAX, ctx->txn_out[ 0 ].details.prep_start_timestamp, microblock_start_ticks );
330 0 : long tx_start_ticks = fd_long_if( ctx->txn_out[ 0 ].details.load_start_timestamp!=LONG_MAX, ctx->txn_out[ 0 ].details.load_start_timestamp, tx_preload_end_ticks );
331 0 : long tx_load_end_ticks = fd_long_if( ctx->txn_out[ 0 ].details.exec_start_timestamp!=LONG_MAX, ctx->txn_out[ 0 ].details.exec_start_timestamp, tx_start_ticks );
332 0 : long tx_end_ticks = fd_long_if( ctx->txn_out[ 0 ].details.commit_start_timestamp!=LONG_MAX, ctx->txn_out[ 0 ].details.commit_start_timestamp, tx_load_end_ticks );
333 :
334 0 : trailer->txn_preload_end_pct = (uchar)(((double)(tx_preload_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
335 0 : trailer->txn_start_pct = (uchar)(((double)(tx_start_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
336 0 : trailer->txn_load_end_pct = (uchar)(((double)(tx_load_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
337 0 : trailer->txn_end_pct = (uchar)(((double)(tx_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
338 :
339 : /* When sending MAX_TXN_PER_MICROBLOCK transactions as fd_txn_p_t to PoH,
340 : there's always extra bytes at the end to stash the trailer. */
341 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_trailer_t), poh_shred_mtu );
342 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_execle_trailer_t), poh_shred_mtu );
343 :
344 : /* We have a race window with the GUI, where if the slot is ending it
345 : will snap these metrics to draw the waterfall, but see them outdated
346 : because housekeeping hasn't run. For now just update them here, but
347 : PoH should eventually flush the pipeline before ending the slot. */
348 0 : metrics_write( ctx );
349 :
350 0 : ulong execle_sig = fd_disco_execle_sig( slot, ctx->_pack_idx );
351 :
352 : /* We always need to publish, even if there are no successfully executed
353 : transactions so the PoH tile can keep an accurate count of microblocks
354 : it has seen. */
355 0 : ulong new_sz = txn_cnt*sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
356 0 : fd_stem_publish( stem, ctx->out_poh->idx, execle_sig, ctx->out_poh->chunk, new_sz, 0UL, (ulong)fd_frag_meta_ts_comp( microblock_start_ticks ), (ulong)fd_frag_meta_ts_comp( tickcount ) );
357 0 : ctx->out_poh->chunk = fd_dcache_compact_next( ctx->out_poh->chunk, new_sz, ctx->out_poh->chunk0, ctx->out_poh->wmark );
358 0 : }
359 :
360 : static inline void
361 : handle_bundle( fd_execle_tile_t * ctx,
362 : ulong seq,
363 : ulong sig,
364 : ulong sz,
365 : ulong begin_tspub,
366 0 : fd_stem_context_t * stem ) {
367 :
368 0 : fd_txn_p_t * txns = (fd_txn_p_t *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
369 :
370 0 : ulong slot = fd_disco_poh_sig_slot( sig );
371 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
372 :
373 0 : fd_bank_t bank[1];
374 0 : FD_TEST( fd_banks_bank_query( bank, ctx->banks, ctx->_bank_idx ) );
375 0 : ulong bank_slot = fd_bank_slot_get( bank );
376 0 : FD_TEST( bank_slot==slot );
377 :
378 0 : fd_acct_addr_t const * writable_alt[ MAX_TXN_PER_MICROBLOCK ];
379 0 : ulong tips [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
380 :
381 : /* Pre-populate ALT accounts for all transactions for rebates.
382 : These were copied in during_frag from the source fd_txn_e_t,
383 : resolved by fd_resolv_tile. */
384 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
385 0 : writable_alt[i] = ctx->_alt_accts[i];
386 0 : }
387 :
388 0 : int execution_success = 1;
389 0 : ulong failed_idx = ULONG_MAX;
390 :
391 : /* Every transaction in the bundle should be executed in order against
392 : different transaciton contexts. */
393 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
394 :
395 0 : fd_txn_p_t * txn = &txns[ i ];
396 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ i ];
397 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ i ];
398 :
399 0 : txn_out->err.txn_err = FD_RUNTIME_EXECUTE_SUCCESS;
400 :
401 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
402 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
403 :
404 0 : if( execution_success==0 ) {
405 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-FD_RUNTIME_TXN_ERR_BUNDLE_PEER)<<24);
406 0 : continue;
407 0 : }
408 :
409 0 : txn_in->txn = txn;
410 0 : txn_in->bundle.is_bundle = 1;
411 :
412 0 : fd_runtime_prepare_and_execute_txn( ctx->runtime, bank, txn_in, txn_out );
413 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
414 0 : if( FD_UNLIKELY( !txn_out->err.is_committable || txn_out->err.txn_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
415 0 : execution_success = 0;
416 0 : failed_idx = i;
417 0 : continue;
418 0 : }
419 0 : }
420 :
421 : /* If all of the transactions in the bundle executed successfully, we
422 : can commit the transactions in order. At this point, we cann also
423 : accumulate unused CUs to the rebate. Otherwise, if any transaction
424 : fails, we need to exclude all the bundle transactions and rebate
425 : all of the CUs. */
426 0 : if( FD_LIKELY( execution_success ) ) {
427 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
428 :
429 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ i ];
430 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ i ];
431 0 : uchar * signature = (uchar *)txn_in->txn->payload + TXN( txn_in->txn )->signature_off;
432 :
433 0 : fd_runtime_commit_txn( ctx->runtime, bank, txn_out );
434 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
435 0 : txns[ i ].flags = (txns[ i ].flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
436 0 : fd_cost_tracker_t * cost_tracker = fd_bank_cost_tracker_locking_modify( bank );
437 0 : int err = fd_cost_tracker_try_add_cost( cost_tracker, txn_out );
438 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txns[ i ].payload, txns[ i ].payload_sz ));
439 0 : FD_BASE58_ENCODE_64_BYTES( signature, signature_b58 );
440 0 : FD_LOG_CRIT(( "transaction %s failed to fit into block despite pack guaranteeing it would "
441 0 : "(res=%d) [block_cost=%lu, vote_cost=%lu, allocated_accounts_data_size=%lu, "
442 0 : "block_cost_limit=%lu, vote_cost_limit=%lu, account_cost_limit=%lu, "
443 0 : "remove_simple_vote_from_cost_model=%i]",
444 0 : signature_b58, err, cost_tracker->block_cost, cost_tracker->vote_cost,
445 0 : cost_tracker->allocated_accounts_data_size,
446 0 : cost_tracker->block_cost_limit, cost_tracker->vote_cost_limit,
447 0 : cost_tracker->account_cost_limit,
448 0 : cost_tracker->remove_simple_vote_from_cost_model ));
449 0 : }
450 :
451 0 : uint actual_execution_cus = (uint)(txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter);
452 0 : uint actual_acct_data_cus = (uint)(txn_out->details.txn_cost.transaction.loaded_accounts_data_size_cost);
453 0 : if( FD_UNLIKELY( fd_txn_is_simple_vote_transaction( TXN( &txns[ i ] ), txns[ i ].payload ) &&
454 0 : !FD_FEATURE_ACTIVE_BANK( bank, remove_simple_vote_from_cost_model ) ) ) {
455 0 : actual_execution_cus = FD_PACK_VOTE_DEFAULT_COMPUTE_UNITS;
456 0 : actual_acct_data_cus = 0U;
457 0 : }
458 :
459 0 : uint requested_exec_plus_acct_data_cus = txns[ i ].pack_cu.requested_exec_plus_acct_data_cus;
460 0 : uint non_execution_cus = txns[ i ].pack_cu.non_execution_cus;
461 0 : txns[ i ].execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - (actual_execution_cus + actual_acct_data_cus);
462 0 : txns[ i ].execle_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus;
463 0 : txns[ i ].flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS | FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
464 0 : tips[ i ] = txn_out->details.tips;
465 :
466 0 : ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_SUCCESS_IDX ]++;
467 0 : ctx->metrics.txn_result[ FD_METRICS_ENUM_TRANSACTION_RESULT_V_SUCCESS_IDX ]++;
468 0 : }
469 0 : } else {
470 0 : FD_TEST( failed_idx != ULONG_MAX );
471 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
472 :
473 0 : ctx->txn_out[ i ].err.is_committable = 0;
474 :
475 : /* If the bundle peer flag is not set, that means the transaction
476 : was at least partially sanitized/setup. We have to cancel
477 : these txns as they will not be included in the block. */
478 :
479 0 : if( i<=failed_idx ) {
480 0 : fd_runtime_cancel_txn( ctx->runtime, &ctx->txn_out[ i ] );
481 0 : }
482 :
483 0 : uint requested_exec_plus_acct_data_cus = txns[ i ].pack_cu.requested_exec_plus_acct_data_cus;
484 0 : uint non_execution_cus = txns[ i ].pack_cu.non_execution_cus;
485 0 : txns[ i ].execle_cu.actual_consumed_cus = 0U;
486 0 : txns[ i ].execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
487 0 : tips[ i ] = 0UL;
488 0 : txns[ i ].flags = fd_uint_if( !!(txns[ i ].flags>>24), txns[ i ].flags, txns[ i ].flags | ((uint)(-FD_RUNTIME_TXN_ERR_BUNDLE_PEER)<<24) );
489 :
490 0 : ctx->metrics.txn_landed[ FD_METRICS_ENUM_TRANSACTION_LANDED_V_UNLANDED_IDX ]++;
491 0 : if( i==failed_idx ) ctx->metrics.txn_result[ fd_execle_err_from_runtime_err( ctx->txn_out[ i ].err.txn_err ) ]++;
492 0 : else ctx->metrics.txn_result[ FD_METRICS_ENUM_TRANSACTION_RESULT_V_BUNDLE_PEER_IDX ]++;
493 0 : }
494 0 : }
495 :
496 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txns, writable_alt, txn_cnt );
497 :
498 : /* Indicate to pack tile we are done processing the transactions so
499 : it can pack new microblocks using these accounts. */
500 0 : fd_fseq_update( ctx->busy_fseq, seq );
501 :
502 : /* We need to publish each transaction separately into its own
503 : microblock, so make a temporary copy on the stack so we can move
504 : all the data around. */
505 0 : fd_txn_p_t bundle_txn_temp[ 5UL ];
506 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
507 0 : bundle_txn_temp[ i ] = txns[ i ];
508 0 : }
509 :
510 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
511 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
512 0 : fd_memcpy( dst, bundle_txn_temp+i, sizeof(fd_txn_p_t) );
513 :
514 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst+sizeof(fd_txn_p_t) );
515 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, 1UL, trailer->hash );
516 0 : trailer->pack_txn_idx = ctx->_txn_idx + i;
517 0 : trailer->tips = tips[ i ];
518 :
519 0 : ulong execle_sig = fd_disco_execle_sig( slot, ctx->_pack_idx+i );
520 :
521 0 : long tickcount = fd_tickcount();
522 0 : long microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, tickcount );
523 0 : long microblock_duration_ticks = fd_long_max(tickcount - microblock_start_ticks, 0L);
524 :
525 0 : long tx_preload_end_ticks = fd_long_if( ctx->txn_out[ i ].details.prep_start_timestamp!=LONG_MAX, ctx->txn_out[ i ].details.prep_start_timestamp, microblock_start_ticks );
526 0 : long tx_start_ticks = fd_long_if( ctx->txn_out[ i ].details.load_start_timestamp!=LONG_MAX, ctx->txn_out[ i ].details.load_start_timestamp, tx_preload_end_ticks );
527 0 : long tx_load_end_ticks = fd_long_if( ctx->txn_out[ i ].details.exec_start_timestamp!=LONG_MAX, ctx->txn_out[ i ].details.exec_start_timestamp, tx_start_ticks );
528 0 : long tx_end_ticks = fd_long_if( ctx->txn_out[ i ].details.commit_start_timestamp!=LONG_MAX, ctx->txn_out[ i ].details.commit_start_timestamp, tx_load_end_ticks );
529 :
530 0 : trailer->txn_preload_end_pct = (uchar)(((double)(tx_preload_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
531 0 : trailer->txn_start_pct = (uchar)(((double)(tx_start_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
532 0 : trailer->txn_load_end_pct = (uchar)(((double)(tx_load_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
533 0 : trailer->txn_end_pct = (uchar)(((double)(tx_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
534 :
535 0 : ulong new_sz = sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
536 0 : fd_stem_publish( stem, ctx->out_poh->idx, execle_sig, ctx->out_poh->chunk, new_sz, 0UL, (ulong)fd_frag_meta_ts_comp( microblock_start_ticks ), (ulong)fd_frag_meta_ts_comp( tickcount ) );
537 0 : ctx->out_poh->chunk = fd_dcache_compact_next( ctx->out_poh->chunk, new_sz, ctx->out_poh->chunk0, ctx->out_poh->wmark );
538 0 : }
539 :
540 0 : metrics_write( ctx );
541 0 : }
542 :
543 : static inline void
544 : after_frag( fd_execle_tile_t * ctx,
545 : ulong in_idx,
546 : ulong seq,
547 : ulong sig,
548 : ulong sz,
549 : ulong tsorig,
550 : ulong tspub,
551 0 : fd_stem_context_t * stem ) {
552 0 : (void)in_idx;
553 :
554 0 : ulong slot = fd_disco_poh_sig_slot( sig );
555 0 : if( FD_UNLIKELY( slot!=ctx->rebates_for_slot ) ) {
556 : /* If pack has already moved on to a new slot, the rebates are no
557 : longer useful. */
558 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_clear( ctx->rebater );
559 0 : ctx->rebates_for_slot = slot;
560 0 : }
561 :
562 0 : if( FD_UNLIKELY( ctx->_is_bundle ) ) handle_bundle( ctx, seq, sig, sz, tspub, stem );
563 0 : else handle_microblock( ctx, seq, sig, sz, tspub, stem );
564 :
565 : /* TODO: Use fancier logic to coalesce rebates e.g. and move this to
566 : after_credit */
567 0 : if( FD_LIKELY( ctx->enable_rebates ) ) {
568 0 : ulong written_sz = 0UL;
569 0 : while( 0UL!=(written_sz=fd_pack_rebate_sum_report( ctx->rebater, fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk ) )) ) {
570 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
571 0 : fd_stem_publish( stem, ctx->out_pack->idx, slot, ctx->out_pack->chunk, written_sz, 0UL, tsorig, tspub );
572 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, written_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
573 0 : }
574 0 : }
575 0 : }
576 :
577 : static inline fd_execle_out_t
578 : out1( fd_topo_t const * topo,
579 : fd_topo_tile_t const * tile,
580 0 : char const * name ) {
581 0 : ulong idx = ULONG_MAX;
582 :
583 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
584 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
585 0 : if( !strcmp( link->name, name ) ) {
586 0 : if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name ));
587 0 : idx = i;
588 0 : }
589 0 : }
590 :
591 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_execle_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
592 :
593 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
594 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
595 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
596 :
597 0 : return (fd_execle_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
598 0 : }
599 :
600 : static void
601 : unprivileged_init( fd_topo_t * topo,
602 0 : fd_topo_tile_t * tile ) {
603 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
604 :
605 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
606 0 : fd_execle_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_execle_tile_t), sizeof(fd_execle_tile_t) );
607 0 : void * blake3 = FD_SCRATCH_ALLOC_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
608 0 : void * bmtree = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
609 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->execle.max_live_slots ) );
610 0 : void * pc_scratch = FD_SCRATCH_ALLOC_APPEND( l, FD_PROGCACHE_SCRATCH_ALIGN, FD_PROGCACHE_SCRATCH_FOOTPRINT );
611 :
612 0 : #define NONNULL( x ) (__extension__({ \
613 0 : __typeof__((x)) __x = (x); \
614 0 : if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
615 0 : __x; }))
616 :
617 0 : ctx->kind_id = tile->kind_id;
618 0 : ctx->blake3 = NONNULL( fd_blake3_join( fd_blake3_new( blake3 ) ) );
619 0 : ctx->bmtree = NONNULL( bmtree );
620 :
621 0 : NONNULL( fd_pack_rebate_sum_join( fd_pack_rebate_sum_new( ctx->rebater ) ) );
622 0 : ctx->rebates_for_slot = 0UL;
623 :
624 0 : fd_accdb_init_from_topo( ctx->accdb, topo, tile, tile->execle.accdb_max_depth );
625 :
626 0 : fd_progcache_init_from_topo( ctx->progcache, topo, pc_scratch, FD_PROGCACHE_SCRATCH_FOOTPRINT );
627 :
628 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->execle.txncache_obj_id );
629 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
630 0 : FD_TEST( txncache_shmem );
631 0 : fd_txncache_t * txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
632 0 : FD_TEST( txncache );
633 :
634 :
635 0 : fd_acc_pool_t * acc_pool = fd_acc_pool_join( fd_topo_obj_laddr( topo, tile->execle.acc_pool_obj_id ) );
636 0 : FD_TEST( acc_pool );
637 :
638 0 : for( ulong i=0UL; i<FD_PACK_MAX_TXN_PER_BUNDLE; i++ ) {
639 0 : ctx->txn_in[ i ].bundle.prev_txn_cnt = i;
640 0 : for( ulong j=0UL; j<i; j++ ) ctx->txn_in[ i ].bundle.prev_txn_outs[ j ] = &ctx->txn_out[ j ];
641 0 : }
642 :
643 0 : ctx->runtime->accdb = ctx->accdb;
644 0 : ctx->runtime->progcache = ctx->progcache;
645 0 : ctx->runtime->status_cache = txncache;
646 0 : ctx->runtime->acc_pool = acc_pool;
647 0 : memset( &ctx->runtime->log, 0, sizeof(ctx->runtime->log) );
648 0 : ctx->runtime->log.log_collector = ctx->log_collector;
649 0 : ctx->runtime->fuzz.enabled = 0;
650 0 : ctx->runtime->accounts.executable_cnt = 0UL;
651 :
652 0 : ulong banks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks" );
653 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
654 0 : ulong banks_locks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks_locks" );
655 0 : FD_TEST( banks_locks_obj_id!=ULONG_MAX );
656 0 : NONNULL( fd_banks_join( ctx->banks, fd_topo_obj_laddr( topo, banks_obj_id ), fd_topo_obj_laddr( topo, banks_locks_obj_id ) ) );
657 :
658 0 : ulong busy_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "execle_busy.%lu", tile->kind_id );
659 0 : FD_TEST( busy_obj_id!=ULONG_MAX );
660 0 : ctx->busy_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, busy_obj_id ) );
661 0 : if( FD_UNLIKELY( !ctx->busy_fseq ) ) FD_LOG_ERR(( "execle tile %lu has no busy flag", tile->kind_id ));
662 :
663 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
664 0 : memset( &ctx->runtime->metrics, 0, sizeof( ctx->runtime->metrics ) );
665 :
666 0 : ctx->pack_in_mem = topo->workspaces[ topo->objs[ topo->links[ tile->in_link_id[ 0UL ] ].dcache_obj_id ].wksp_id ].wksp;
667 0 : ctx->pack_in_chunk0 = fd_dcache_compact_chunk0( ctx->pack_in_mem, topo->links[ tile->in_link_id[ 0UL ] ].dcache );
668 0 : ctx->pack_in_wmark = fd_dcache_compact_wmark ( ctx->pack_in_mem, topo->links[ tile->in_link_id[ 0UL ] ].dcache, topo->links[ tile->in_link_id[ 0UL ] ].mtu );
669 :
670 0 : *ctx->out_poh = out1( topo, tile, "execle_poh" ); FD_TEST( ctx->out_poh->idx!=ULONG_MAX );
671 0 : *ctx->out_pack = out1( topo, tile, "execle_pack" );
672 :
673 0 : ctx->enable_rebates = ctx->out_pack->idx!=ULONG_MAX;
674 0 : }
675 :
676 : static ulong
677 : populate_allowed_seccomp( fd_topo_t const * topo,
678 : fd_topo_tile_t const * tile,
679 : ulong out_cnt,
680 0 : struct sock_filter * out ) {
681 0 : (void)topo;
682 0 : (void)tile;
683 :
684 0 : populate_sock_filter_policy_fd_execle_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
685 0 : return sock_filter_policy_fd_execle_tile_instr_cnt;
686 0 : }
687 :
688 : static ulong
689 : populate_allowed_fds( fd_topo_t const * topo,
690 : fd_topo_tile_t const * tile,
691 : ulong out_fds_cnt,
692 0 : int * out_fds ) {
693 0 : (void)topo;
694 0 : (void)tile;
695 :
696 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
697 :
698 0 : ulong out_cnt = 0UL;
699 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
700 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
701 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
702 0 : return out_cnt;
703 0 : }
704 :
705 : /* For a bundle, one bundle might burst into at most 5 separate PoH mixins, since the
706 : microblocks cannot be conflicting. */
707 :
708 0 : #define STEM_BURST (5UL)
709 :
710 : /* See explanation in fd_pack */
711 0 : #define STEM_LAZY (128L*3000L)
712 :
713 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_execle_tile_t
714 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_execle_tile_t)
715 :
716 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
717 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
718 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
719 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
720 :
721 : #include "../../disco/stem/fd_stem.c"
722 :
723 : fd_topo_run_tile_t fd_tile_execle = {
724 : .name = "execle",
725 : .populate_allowed_seccomp = populate_allowed_seccomp,
726 : .populate_allowed_fds = populate_allowed_fds,
727 : .scratch_align = scratch_align,
728 : .scratch_footprint = scratch_footprint,
729 : .unprivileged_init = unprivileged_init,
730 : .run = stem_run,
731 : };
|