Line data Source code
1 : #include "fd_execle_err.h"
2 :
3 : #include "../../disco/tiles.h"
4 : #include "../../disco/pack/fd_pack.h"
5 : #include "../../disco/pack/fd_pack_cost.h"
6 : #include "../../ballet/blake3/fd_blake3.h"
7 : #include "../../ballet/bmtree/fd_bmtree.h"
8 : #include "../../disco/metrics/fd_metrics.h"
9 : #include "../../disco/events/generated/fd_event_gen.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_startup.h"
14 : #include "../../flamenco/runtime/fd_runtime.h"
15 : #include "../../flamenco/runtime/fd_bank.h"
16 : #include "../../flamenco/progcache/fd_progcache_user.h"
17 : #include "../../flamenco/log_collector/fd_log_collector_base.h"
18 : #include <time.h>
19 : #include "generated/fd_execle_tile_seccomp.h"
20 :
21 0 : #define REBATE_BATCH_IDLE_LOOPS (128UL)
22 0 : #define REBATE_BATCH_MAX_MICROBLOCKS (16UL)
23 :
24 : FD_STATIC_ASSERT( REBATE_BATCH_MAX_MICROBLOCKS*FD_PACK_REBATE_MAX_ENTRIES<=FD_PACK_REBATE_SUM_CAPACITY,
25 : rebate_batch_fits_rebater );
26 :
27 : struct fd_execle_out {
28 : ulong idx;
29 : fd_wksp_t * mem;
30 : ulong chunk0;
31 : ulong wmark;
32 : ulong chunk;
33 : };
34 :
35 : typedef struct fd_execle_out fd_execle_out_t;
36 :
37 : struct fd_execle_tile {
38 : ulong kind_id;
39 :
40 : fd_startup_gate_t startup_gate[1];
41 :
42 : fd_blake3_t * blake3;
43 : void * bmtree;
44 :
45 : ulong _bank_idx;
46 : ulong _pack_idx;
47 : ulong _txn_idx;
48 : int _is_bundle;
49 : fd_acct_addr_t _alt_accts[MAX_TXN_PER_MICROBLOCK][FD_TXN_ACCT_ADDR_MAX];
50 :
51 : ulong * busy_fseq;
52 : ulong * pack_in_fseq;
53 :
54 : fd_wksp_t * pack_in_mem;
55 : ulong pack_in_chunk0;
56 : ulong pack_in_wmark;
57 :
58 : fd_execle_out_t out_poh[1];
59 : fd_execle_out_t out_pack[1];
60 :
61 : ulong rebates_for_slot;
62 : ulong rebate_tsorig;
63 : ulong rebate_microblock_cnt;
64 : ulong rebate_idle_loop_cnt;
65 : int rebate_draining;
66 : int enable_rebates;
67 : ulong rebate_seed;
68 : fd_pack_rebate_sum_t rebater[ 1 ];
69 :
70 : fd_banks_t * banks;
71 : fd_accdb_t * accdb;
72 :
73 : fd_progcache_t progcache[1];
74 :
75 : fd_runtime_t runtime[1];
76 :
77 : /* For bundle execution, we need to execute each transaction against
78 : a separate transaction context and a set of accounts, but the exec
79 : stack can be reused. We will also use these same memory regions
80 : for non-bundle execution. */
81 : fd_txn_in_t txn_in[ FD_PACK_MAX_TXN_PER_BUNDLE ];
82 : fd_txn_out_t txn_out[ FD_PACK_MAX_TXN_PER_BUNDLE ];
83 :
84 : fd_log_collector_t log_collector[ 1 ];
85 :
86 : float ns_per_tick;
87 :
88 : struct {
89 : /* Ticks spent loading txn accounts */
90 : ulong txn_load_cum_ticks;
91 :
92 : /* Ticks spent validating txn invariants (e.g. status cache, fee payer) */
93 : ulong txn_check_cum_ticks;
94 :
95 : /* Ticks spent executing a txn (includes any VM time) */
96 : ulong txn_exec_cum_ticks;
97 :
98 : /* Ticks spent committing a txn (database writes) */
99 : ulong txn_commit_cum_ticks;
100 : } metrics;
101 :
102 : };
103 :
104 : typedef struct fd_execle_tile fd_execle_tile_t;
105 :
106 : FD_FN_CONST static inline ulong
107 0 : scratch_align( void ) {
108 0 : return 128UL;
109 0 : }
110 :
111 : FD_FN_PURE static inline ulong
112 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
113 0 : ulong l = FD_LAYOUT_INIT;
114 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_execle_tile_t ), sizeof( fd_execle_tile_t ) );
115 0 : l = FD_LAYOUT_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
116 0 : l = FD_LAYOUT_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
117 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->execle.max_live_slots ) );
118 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->execle.max_live_slots ) );
119 0 : l = FD_LAYOUT_APPEND( l, FD_PROGCACHE_SCRATCH_ALIGN, FD_PROGCACHE_SCRATCH_FOOTPRINT );
120 0 : return FD_LAYOUT_FINI( l, scratch_align() );
121 0 : }
122 :
123 : static inline void
124 0 : metrics_write( fd_execle_tile_t * ctx ) {
125 0 : fd_accdb_flush_metrics( ctx->accdb );
126 :
127 0 : FD_MCNT_SET( EXECLE, INSTRUCTION_EXECUTED, ctx->runtime->metrics.instr_cum );
128 0 : FD_MCNT_SET( EXECLE, CPI_EXECUTED, ctx->runtime->metrics.cpi_cum );
129 :
130 0 : FD_MCNT_SET( EXECLE, CU_EXECUTED, ctx->runtime->metrics.cu_cum );
131 :
132 0 : FD_MCNT_SET( EXECLE, TXN_REGIME_DURATION_NANOS_SETUP, ctx->metrics.txn_check_cum_ticks+ctx->metrics.txn_load_cum_ticks );
133 0 : FD_MCNT_SET( EXECLE, TXN_REGIME_DURATION_NANOS_EXEC, ctx->metrics.txn_exec_cum_ticks );
134 0 : FD_MCNT_SET( EXECLE, TXN_REGIME_DURATION_NANOS_COMMIT, ctx->metrics.txn_commit_cum_ticks );
135 :
136 0 : fd_runtime_t const * runtime = ctx->runtime;
137 0 : ulong cpi_ticks = runtime->metrics.cpi_setup_cum_ticks + runtime->metrics.cpi_commit_cum_ticks;
138 0 : ulong exec_ticks = fd_ulong_sat_sub( runtime->metrics.vm_exec_cum_ticks, cpi_ticks );
139 0 : FD_MCNT_SET( EXECLE, VM_REGIME_DURATION_NANOS_SETUP, runtime->metrics.vm_setup_cum_ticks );
140 0 : FD_MCNT_SET( EXECLE, VM_REGIME_DURATION_NANOS_COMMIT, runtime->metrics.vm_commit_cum_ticks );
141 0 : FD_MCNT_SET( EXECLE, VM_REGIME_DURATION_NANOS_SETUP_CPI, runtime->metrics.cpi_setup_cum_ticks );
142 0 : FD_MCNT_SET( EXECLE, VM_REGIME_DURATION_NANOS_COMMIT_CPI, runtime->metrics.cpi_commit_cum_ticks );
143 0 : FD_MCNT_SET( EXECLE, VM_REGIME_DURATION_NANOS_INTERPRETER, exec_ticks );
144 :
145 0 : FD_PROGCACHE_METRICS_WRITE( EXECLE, ctx->progcache->metrics );
146 :
147 0 : FD_ACCDB_METRICS_WRITE( EXECLE, fd_accdb_metrics( ctx->accdb ) );
148 0 : }
149 :
150 : static inline void
151 : after_credit( fd_execle_tile_t * ctx,
152 : fd_stem_context_t * stem,
153 : int * opt_poll_in,
154 0 : int * charge_busy ) {
155 0 : fd_startup_gate_idle( ctx->startup_gate );
156 :
157 0 : if( FD_UNLIKELY( !ctx->enable_rebates || !ctx->rebate_microblock_cnt ) ) return;
158 :
159 0 : if( FD_LIKELY( !ctx->rebate_draining ) ) {
160 0 : if( FD_LIKELY( ctx->rebate_microblock_cnt<REBATE_BATCH_MAX_MICROBLOCKS &&
161 0 : ctx->rebate_idle_loop_cnt<REBATE_BATCH_IDLE_LOOPS &&
162 0 : !ctx->rebater->ib_result ) ) {
163 0 : ctx->rebate_idle_loop_cnt++;
164 0 : return;
165 0 : }
166 0 : ctx->rebate_draining = 1;
167 0 : }
168 :
169 0 : ulong written_sz = fd_pack_rebate_sum_report( ctx->rebater, fd_chunk_to_laddr( ctx->out_pack->mem, ctx->out_pack->chunk ) );
170 0 : if( FD_UNLIKELY( written_sz ) ) {
171 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
172 0 : fd_stem_publish( stem, ctx->out_pack->idx, ctx->rebates_for_slot, ctx->out_pack->chunk, written_sz, 0UL, ctx->rebate_tsorig, tspub );
173 0 : ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, written_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark );
174 0 : *opt_poll_in = 0;
175 0 : *charge_busy = 1;
176 0 : } else {
177 0 : ctx->rebate_microblock_cnt = 0UL;
178 0 : ctx->rebate_idle_loop_cnt = 0UL;
179 0 : ctx->rebate_draining = 0;
180 0 : }
181 0 : }
182 :
183 : static int
184 : before_frag( fd_execle_tile_t * ctx,
185 : ulong in_idx,
186 : ulong seq,
187 0 : ulong sig ) {
188 0 : (void)in_idx; (void)seq;
189 :
190 0 : fd_startup_gate_busy( ctx->startup_gate );
191 :
192 : /* Pack also outputs "leader slot done" which we can ignore. */
193 0 : if( FD_UNLIKELY( fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_MICROBLOCK ) ) return 1;
194 :
195 0 : ulong target_execle_kind_id = fd_disco_poh_sig_execle_tile( sig );
196 0 : if( FD_UNLIKELY( target_execle_kind_id!=ctx->kind_id ) ) return 1;
197 :
198 0 : return 0;
199 0 : }
200 :
201 : static inline void
202 : during_frag( fd_execle_tile_t * ctx,
203 : ulong in_idx,
204 : ulong seq,
205 : ulong sig,
206 : ulong chunk,
207 : ulong sz,
208 0 : ulong ctl ) {
209 0 : (void)in_idx; (void)seq; (void)sig; (void)ctl;
210 :
211 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->pack_in_mem, chunk );
212 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
213 :
214 0 : if( FD_UNLIKELY( chunk<ctx->pack_in_chunk0 || chunk>ctx->pack_in_wmark || sz>USHORT_MAX ) )
215 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->pack_in_chunk0, ctx->pack_in_wmark ));
216 :
217 : /* Pack sends fd_txn_e_t (with ALT accounts), but PoH expects fd_txn_p_t.
218 : We copy the fd_txn_p_t portion to the PoH output buffer, and copy the
219 : ALT accounts to the tile context for rebates. */
220 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
221 0 : fd_txn_e_t const * src_txn_e = (fd_txn_e_t const *)src;
222 0 : fd_txn_p_t * dst_txn_p = (fd_txn_p_t *)dst;
223 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
224 0 : fd_txn_p_t const * s = src_txn_e[i].txnp;
225 0 : fd_txn_p_t * d = dst_txn_p + i;
226 0 : fd_txn_t const * t = TXN( s );
227 0 : ulong payload_sz = fd_ulong_min( s->payload_sz, FD_TPU_MTU );
228 0 : ulong txn_sz = fd_ulong_min( fd_txn_footprint( t->instr_cnt, t->addr_table_lookup_cnt ), FD_TXN_MAX_SZ );
229 0 : fd_memcpy( d->payload, s->payload, payload_sz );
230 0 : fd_memcpy( &d->payload_sz, &s->payload_sz, offsetof(fd_txn_p_t, _)-offsetof(fd_txn_p_t, payload_sz) );
231 0 : fd_memcpy( d->_, s->_, txn_sz );
232 0 : ulong alt_cnt = fd_ulong_min( (ulong)t->addr_table_adtl_cnt, FD_TXN_ACCT_ADDR_MAX );
233 0 : fd_memcpy( ctx->_alt_accts[i], src_txn_e[i].alt_accts, alt_cnt * sizeof(fd_acct_addr_t) );
234 0 : }
235 :
236 0 : fd_microblock_execle_trailer_t * trailer = (fd_microblock_execle_trailer_t *)( src+sz-sizeof(fd_microblock_execle_trailer_t) );
237 0 : ctx->_bank_idx = trailer->bank_idx;
238 0 : ctx->_pack_idx = trailer->pack_idx;
239 0 : ctx->_txn_idx = trailer->pack_txn_idx;
240 0 : ctx->_is_bundle = trailer->is_bundle;
241 0 : }
242 :
243 : static void
244 : hash_transactions( void * mem,
245 : fd_txn_p_t * txns,
246 : ulong txn_cnt,
247 0 : uchar * mixin ) {
248 0 : fd_bmtree_commit_t * bmtree = fd_bmtree_commit_init( mem, 32UL, 1UL, 0UL );
249 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
250 0 : fd_txn_p_t * _txn = txns + i;
251 0 : if( FD_UNLIKELY( !(_txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
252 :
253 0 : fd_txn_t * txn = TXN(_txn);
254 0 : for( ulong j=0; j<txn->signature_cnt; j++ ) {
255 0 : fd_bmtree_node_t node[1];
256 0 : fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+64UL*j, 64UL, 1UL );
257 0 : fd_bmtree_commit_append( bmtree, node, 1UL );
258 0 : }
259 0 : }
260 0 : if( FD_LIKELY( fd_bmtree_commit_leaf_cnt( bmtree ) ) ) {
261 0 : uchar * root = fd_bmtree_commit_fini( bmtree );
262 0 : fd_memcpy( mixin, root, 32UL );
263 0 : } else {
264 0 : fd_memset( mixin, 0, 32UL );
265 : /* If FD_HAS_MSAN, poison so we can detect if this is ever accessed upstream, even though it should never be. In production, this is a no-op. */
266 0 : fd_msan_poison( mixin, 32UL );
267 0 : }
268 0 : }
269 :
270 : static inline void
271 : handle_microblock( fd_execle_tile_t * ctx,
272 : ulong seq,
273 : ulong sig,
274 : ulong sz,
275 : ulong begin_tspub,
276 0 : fd_stem_context_t * stem ) {
277 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
278 :
279 0 : ulong slot = fd_disco_poh_sig_slot( sig );
280 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
281 :
282 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->_bank_idx );
283 0 : FD_TEST( bank );
284 0 : ulong bank_slot = bank->f.slot;
285 0 : FD_TEST( bank_slot==slot );
286 :
287 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst + txn_cnt*sizeof(fd_txn_p_t) );
288 0 : trailer->txn_ns_dt = (fd_txn_ns_dt_t){0};
289 0 : trailer->bank_seq = bank->bank_seq;
290 0 : trailer->exec_end_ticks = LONG_MAX;
291 :
292 : /* exec_start_ticks is the first transaction's load_start_ticks (an
293 : empty microblock reads the clock itself); microblock_start_ticks
294 : stays pack's publish time, decompressed against it. */
295 0 : long exec_start_ticks = txn_cnt ? 0L : fd_tickcount();
296 0 : long microblock_start_ticks = txn_cnt ? 0L : fd_frag_meta_ts_decomp( begin_tspub, exec_start_ticks );
297 0 : long last_ticks = exec_start_ticks;
298 :
299 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
300 0 : fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );
301 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ 0 ];
302 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ 0 ];
303 :
304 0 : uint const requested_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
305 0 : uint const non_execution_cus = txn->pack_cu.non_execution_cus;
306 :
307 : /* Assume failure, set below if success. If it doesn't land in the
308 : block, rebate the non-execution CUs too. */
309 0 : txn->execle_cu.actual_consumed_cus = 0U;
310 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
311 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
312 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
313 :
314 0 : txn_in->bundle.is_bundle = 0;
315 0 : txn_in->txn = txn;
316 0 : txn_in->index_in_slot = ctx->_txn_idx + i;
317 :
318 0 : fd_runtime_prepare_and_execute_txn( ctx->runtime, bank, txn_in, txn_out );
319 :
320 0 : if( FD_UNLIKELY( !i ) ) {
321 0 : exec_start_ticks = txn_out->details.load_start_ticks;
322 0 : microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, exec_start_ticks );
323 0 : last_ticks = exec_start_ticks;
324 0 : }
325 :
326 : /* Stash the result in the flags value so that pack can inspect it. */
327 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
328 :
329 : /* Drop no-op transactions, we don't want to include these as they
330 : charge no fees. */
331 0 : if( FD_UNLIKELY( txn_out->err.is_noop ) ) {
332 0 : txn_out->err.is_committable = 0;
333 0 : }
334 :
335 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_VERSION )+fd_execle_version_from_txn( TXN( txn ) ) ]++;
336 :
337 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
338 0 : FD_TEST( !txn_out->err.is_fees_only );
339 0 : fd_runtime_cancel_txn( ctx->runtime, bank, txn_in, txn_out );
340 : /* Use pre-resolved ALT accounts for rebates even for unlanded transactions */
341 0 : fd_acct_addr_t const * writable_alt = ctx->_alt_accts[i];
342 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txn, &writable_alt, 1UL );
343 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_UNLANDED_IDX ]++;
344 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+(ulong)fd_execle_err_from_runtime_err( txn_out->err.txn_err ) ]++;
345 0 : last_ticks = fd_tickcount();
346 0 : continue;
347 0 : }
348 :
349 : /* Check for FeesOnly transactions where actual CUs exceed
350 : requested. This can happen for durable nonce transactions
351 : with oversized nonce accounts. Skip the commit to prevent
352 : underflow in the rebate computation. The transaction keeps its
353 : default full rebate set at the top of the loop. */
354 0 : if( FD_UNLIKELY( txn_out->err.is_fees_only ) ) {
355 0 : uint fee_only_actual_exec_cus = (uint)(txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter);
356 0 : uint fee_only_actual_data_cus = (uint)(txn_out->details.txn_cost.transaction.loaded_accounts_data_size_cost);
357 0 : if( FD_UNLIKELY( fee_only_actual_exec_cus + fee_only_actual_data_cus > requested_exec_plus_acct_data_cus ) ) {
358 0 : FD_LOG_WARNING(( "FeesOnly txn actual CUs (%u+%u) exceed requested (%u), dropping",
359 0 : fee_only_actual_exec_cus, fee_only_actual_data_cus, requested_exec_plus_acct_data_cus ));
360 0 : txn_out->err.is_committable = 0;
361 0 : fd_runtime_cancel_txn( ctx->runtime, bank, txn_in, txn_out );
362 : /* txn->execle_cu already initialized to full rebate at top of loop */
363 0 : fd_acct_addr_t const * writable_alt = ctx->_alt_accts[i];
364 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txn, &writable_alt, 1UL );
365 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_UNLANDED_IDX ]++;
366 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+(ulong)fd_execle_err_from_runtime_err( txn_out->err.txn_err ) ]++;
367 : /* FD_TXN_P_FLAGS_EXECUTE_SUCCESS = 0 ensures txn won't be
368 : mixed-in by POH */
369 0 : last_ticks = fd_tickcount();
370 0 : continue;
371 0 : }
372 0 : }
373 :
374 0 : if( FD_UNLIKELY( txn_out->err.is_fees_only ) ) fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_FEES_ONLY_IDX ]++;
375 0 : else if( FD_UNLIKELY( txn_out->err.txn_err ) ) fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_FAILED_IDX ]++;
376 0 : else fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_SUCCESS_IDX ]++;
377 :
378 : /* TXN_P_FLAGS_EXECUTE_SUCCESS means that it should be included in
379 : the block. It's a bit of a misnomer now that there are fee-only
380 : transactions. */
381 0 : txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS | FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
382 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+(ulong)fd_execle_err_from_runtime_err( txn_out->err.txn_err ) ]++;
383 :
384 : /* Commit must succeed so no failure path. Once commit is called,
385 : the transactions MUST be mixed into the PoH otherwise we will
386 : fork and diverge, so the link from here til PoH mixin must be
387 : completely reliable with nothing dropped.
388 :
389 : fd_runtime_commit_txn checks if the transaction fits into the
390 : block with the cost tracker. If it doesn't fit, flags is set to
391 : zero. A key invariant of the leader pipeline is that pack
392 : ensures all transactions must fit already, so it is a fatal error
393 : if that happens. We cannot reject the transaction here as there
394 : would be no way to undo the partially applied changes to the bank
395 : in finalize anyway. */
396 0 : fd_runtime_commit_txn( ctx->runtime, bank, txn_in, txn_out );
397 :
398 0 : long const txn_end_ticks = fd_tickcount();
399 0 : last_ticks = txn_end_ticks;
400 :
401 0 : ulong const load_ticks_dt = fd_ulong_if( txn_out->details.check_start_ticks==LONG_MAX || txn_out->details.load_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.check_start_ticks - txn_out->details.load_start_ticks ) );
402 0 : ulong const check_ticks_dt = fd_ulong_if( txn_out->details.exec_start_ticks==LONG_MAX || txn_out->details.check_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.exec_start_ticks - txn_out->details.check_start_ticks ) );
403 0 : ulong const exec_ticks_dt = fd_ulong_if( txn_out->details.commit_start_ticks==LONG_MAX || txn_out->details.exec_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.commit_start_ticks - txn_out->details.exec_start_ticks ) );
404 0 : ulong const commit_ticks_dt = fd_ulong_if( txn_end_ticks==LONG_MAX || txn_out->details.commit_start_ticks==LONG_MAX, 0UL, (ulong)( txn_end_ticks - txn_out->details.commit_start_ticks ) );
405 :
406 0 : ctx->metrics.txn_load_cum_ticks += load_ticks_dt;
407 0 : ctx->metrics.txn_check_cum_ticks += check_ticks_dt;
408 0 : ctx->metrics.txn_exec_cum_ticks += exec_ticks_dt;
409 0 : ctx->metrics.txn_commit_cum_ticks += commit_ticks_dt;
410 :
411 0 : trailer->txn_ns_dt.load_start = fd_float_if( txn_out->details.load_start_ticks==LONG_MAX, 0., (float)fd_long_max( 0L, txn_out->details.load_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
412 0 : trailer->txn_ns_dt.check_start = fd_float_if( txn_out->details.check_start_ticks==LONG_MAX, trailer->txn_ns_dt.load_start, (float)fd_long_max( 0L, txn_out->details.check_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
413 0 : trailer->txn_ns_dt.exec_start = fd_float_if( txn_out->details.exec_start_ticks==LONG_MAX, trailer->txn_ns_dt.check_start, (float)fd_long_max( 0L, txn_out->details.exec_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
414 0 : trailer->txn_ns_dt.commit_start = fd_float_if( txn_out->details.commit_start_ticks==LONG_MAX, trailer->txn_ns_dt.exec_start, (float)fd_long_max( 0L, txn_out->details.commit_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
415 0 : trailer->txn_ns_dt.commit_end = fd_float_if( txn_end_ticks==LONG_MAX, trailer->txn_ns_dt.commit_start, (float)fd_long_max( 0L, txn_end_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
416 0 : trailer->exec_end_ticks = txn_end_ticks;
417 :
418 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
419 : /* If the transaction failed to fit into the block, we need to
420 : updated the transaction flag with the error code. */
421 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
422 0 : fd_cost_tracker_t * cost_tracker = fd_bank_cost_tracker_modify( bank );
423 0 : uchar * signature = (uchar *)txn_in->txn->payload + TXN( txn_in->txn )->signature_off;
424 0 : int err = fd_cost_tracker_try_add_cost( cost_tracker, txn_out );
425 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
426 0 : FD_BASE58_ENCODE_64_BYTES( signature, signature_b58 );
427 0 : FD_LOG_CRIT(( "transaction %s failed to fit into block despite pack guaranteeing it would "
428 0 : "(res=%d) [block_cost=%lu, allocated_accounts_data_size=%lu, "
429 0 : "block_cost_limit=%lu, account_cost_limit=%lu]",
430 0 : signature_b58, err, cost_tracker->block_cost,
431 0 : cost_tracker->allocated_accounts_data_size,
432 0 : cost_tracker->block_cost_limit,
433 0 : cost_tracker->account_cost_limit ));
434 0 : }
435 :
436 0 : uint actual_execution_cus = (uint)(txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter);
437 0 : uint actual_acct_data_cus = (uint)(txn_out->details.txn_cost.transaction.loaded_accounts_data_size_cost);
438 :
439 : /* The VM will stop executing and fail an instruction immediately if
440 : it exceeds its requested CUs. A transaction which requests less
441 : account data than it actually consumes will fail in the account
442 : loading stage. */
443 0 : if( FD_UNLIKELY( actual_execution_cus + actual_acct_data_cus > requested_exec_plus_acct_data_cus ) ) {
444 0 : uchar * _signature = (uchar *)txn->payload + TXN( txn )->signature_off;
445 0 : FD_BASE58_ENCODE_64_BYTES( _signature, _signature_b58 );
446 0 : fd_cost_tracker_t const * _ct = fd_bank_cost_tracker_query( bank );
447 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
448 0 : FD_LOG_ERR(( "transaction %s actual CUs (%u+%u) exceeded requested (%u) despite pack guaranteeing it would fit "
449 0 : "[is_simple_vote=%i, is_fees_only=%i, block_cost=%lu, block_cost_limit=%lu, account_cost_limit=%lu]",
450 0 : _signature_b58, actual_execution_cus, actual_acct_data_cus, requested_exec_plus_acct_data_cus,
451 0 : fd_txn_is_simple_vote_transaction( TXN(txn), txn->payload ),
452 0 : txn_out->err.is_fees_only, _ct->block_cost, _ct->block_cost_limit, _ct->account_cost_limit ));
453 0 : }
454 :
455 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - (actual_execution_cus + actual_acct_data_cus);
456 0 : txn->execle_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus;
457 :
458 : /* Use ALT accounts copied in during_frag for rebates.
459 : These were resolved by resolv_tile and are needed because the LUT
460 : may be deactivated by the time we get here. */
461 0 : fd_acct_addr_t const * writable_alt = ctx->_alt_accts[i];
462 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txn, &writable_alt, 1UL );
463 0 : }
464 :
465 : /* Indicate to pack tile we are done processing the transactions so
466 : it can pack new microblocks using these accounts. */
467 0 : fd_fseq_update( ctx->busy_fseq, seq );
468 :
469 : /* Now produce the merkle hash of the transactions for inclusion
470 : (mixin) to the PoH hash. This is done on the execle tile because
471 : it shards / scales horizontally here, while PoH does not. */
472 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, txn_cnt, trailer->hash );
473 0 : trailer->pack_txn_idx = ctx->_txn_idx;
474 0 : trailer->tips = ctx->txn_out[ 0 ].details.tips;
475 0 : trailer->exec_start_ticks = exec_start_ticks;
476 :
477 : /* When sending MAX_TXN_PER_MICROBLOCK transactions as fd_txn_p_t to PoH,
478 : there's always extra bytes at the end to stash the trailer. */
479 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_trailer_t), poh_shred_mtu );
480 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 );
481 :
482 0 : ulong execle_sig = fd_disco_execle_sig( slot, ctx->_pack_idx );
483 :
484 : /* We always need to publish, even if there are no successfully executed
485 : transactions so the PoH tile can keep an accurate count of microblocks
486 : it has seen. PoH ignores tspub and the GUI takes it as the end of
487 : execution, which every path through the loop leaves in last_ticks. */
488 0 : ulong new_sz = txn_cnt*sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
489 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( last_ticks ) );
490 0 : ctx->out_poh->chunk = fd_dcache_compact_next( ctx->out_poh->chunk, new_sz, ctx->out_poh->chunk0, ctx->out_poh->wmark );
491 0 : }
492 :
493 : static inline void
494 : handle_bundle( fd_execle_tile_t * ctx,
495 : ulong seq,
496 : ulong sig,
497 : ulong sz,
498 : ulong begin_tspub,
499 0 : fd_stem_context_t * stem ) {
500 0 : long const bundle_start_ticks = fd_tickcount();
501 0 : long const microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, bundle_start_ticks );
502 :
503 0 : fd_txn_p_t * txns = (fd_txn_p_t *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
504 :
505 0 : ulong slot = fd_disco_poh_sig_slot( sig );
506 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
507 :
508 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->_bank_idx );
509 0 : FD_TEST( bank );
510 0 : ulong bank_slot = bank->f.slot;
511 0 : FD_TEST( bank_slot==slot );
512 :
513 0 : fd_acct_addr_t const * writable_alt [ MAX_TXN_PER_MICROBLOCK ];
514 0 : ulong tips [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
515 0 : long txn_end_ticks[ MAX_TXN_PER_MICROBLOCK ];
516 0 : for( ulong i=0UL; i<MAX_TXN_PER_MICROBLOCK; i++ ) txn_end_ticks[ i ] = LONG_MAX;
517 :
518 : /* Pre-populate ALT accounts for all transactions for rebates.
519 : These were copied in during_frag from the source fd_txn_e_t,
520 : resolved by fd_resolv_tile. */
521 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
522 0 : writable_alt[i] = ctx->_alt_accts[i];
523 0 : ctx->txn_in[ i ].txn = &txns[ i ];
524 0 : ctx->txn_in[ i ].bundle.is_bundle = 1;
525 0 : ctx->txn_in[ i ].index_in_slot = ctx->_txn_idx + i;
526 0 : }
527 :
528 0 : int execution_success = 1;
529 0 : ulong failed_idx = ULONG_MAX;
530 :
531 : /* Acquire all accdb resources in order to execute the bundle. */
532 0 : int setup_bundle = 1;
533 0 : int err = fd_runtime_prepare_bundle_accounts( ctx->runtime, bank, ctx->txn_in, ctx->txn_out, txn_cnt );
534 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
535 0 : execution_success = 0;
536 0 : failed_idx = 0;
537 0 : setup_bundle = 0;
538 0 : }
539 :
540 : /* Every transaction in the bundle should be executed in order against
541 : different transaction contexts. */
542 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
543 :
544 0 : fd_txn_p_t * txn = &txns[ i ];
545 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ i ];
546 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ i ];
547 :
548 0 : txn_out->err.txn_err = FD_RUNTIME_EXECUTE_SUCCESS;
549 :
550 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
551 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
552 :
553 0 : if( execution_success==0 ) {
554 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-FD_RUNTIME_TXN_ERR_BUNDLE_PEER)<<24);
555 0 : continue;
556 0 : }
557 :
558 0 : txn_in->txn = txn;
559 0 : txn_in->bundle.is_bundle = 1;
560 :
561 0 : fd_runtime_prepare_and_execute_txn( ctx->runtime, bank, txn_in, txn_out );
562 :
563 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
564 0 : if( FD_UNLIKELY( !txn_out->err.is_committable || txn_out->err.txn_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
565 0 : execution_success = 0;
566 0 : failed_idx = i;
567 0 : continue;
568 0 : }
569 0 : }
570 :
571 : /* If all of the transactions in the bundle executed successfully, we
572 : can commit the transactions in order. At this point, we can also
573 : accumulate unused CUs to the rebate. Otherwise, if any transaction
574 : fails, we need to exclude all the bundle transactions and rebate
575 : all of the CUs. */
576 0 : if( FD_LIKELY( execution_success ) ) {
577 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
578 :
579 0 : fd_txn_in_t * txn_in = &ctx->txn_in[ i ];
580 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ i ];
581 0 : uchar * signature = (uchar *)txn_in->txn->payload + TXN( txn_in->txn )->signature_off;
582 :
583 0 : fd_runtime_commit_txn( ctx->runtime, bank, txn_in, txn_out );
584 :
585 0 : txn_end_ticks[ i ] = fd_tickcount();
586 :
587 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
588 0 : txns[ i ].flags = (txns[ i ].flags & 0x00FFFFFFU) | ((uint)(-txn_out->err.txn_err)<<24);
589 0 : fd_cost_tracker_t * cost_tracker = fd_bank_cost_tracker_modify( bank );
590 0 : int err = fd_cost_tracker_try_add_cost( cost_tracker, txn_out );
591 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txns[ i ].payload, txns[ i ].payload_sz ));
592 0 : FD_BASE58_ENCODE_64_BYTES( signature, signature_b58 );
593 0 : FD_LOG_CRIT(( "transaction %s failed to fit into block despite pack guaranteeing it would "
594 0 : "(res=%d) [block_cost=%lu, allocated_accounts_data_size=%lu, "
595 0 : "block_cost_limit=%lu, account_cost_limit=%lu]",
596 0 : signature_b58, err, cost_tracker->block_cost,
597 0 : cost_tracker->allocated_accounts_data_size,
598 0 : cost_tracker->block_cost_limit, cost_tracker->account_cost_limit ));
599 0 : }
600 :
601 0 : uint actual_execution_cus = (uint)(txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter);
602 0 : uint actual_acct_data_cus = (uint)(txn_out->details.txn_cost.transaction.loaded_accounts_data_size_cost);
603 0 : uint non_execution_cus = txns[ i ].pack_cu.non_execution_cus;
604 0 : uint requested_exec_plus_acct_data_cus = txns[ i ].pack_cu.requested_exec_plus_acct_data_cus;
605 :
606 0 : if( FD_UNLIKELY( actual_execution_cus + actual_acct_data_cus > requested_exec_plus_acct_data_cus ) ) {
607 0 : FD_BASE58_ENCODE_64_BYTES( signature, signature_b58 );
608 0 : fd_cost_tracker_t const * _ct = fd_bank_cost_tracker_query( bank );
609 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txns[ i ].payload, txns[ i ].payload_sz ));
610 0 : FD_LOG_ERR(( "transaction %s actual CUs (%u+%u) exceeded requested (%u) despite pack guaranteeing it would "
611 0 : "fit [block_cost=%lu, block_cost_limit=%lu, account_cost_limit=%lu]",
612 0 : signature_b58, actual_execution_cus, actual_acct_data_cus, requested_exec_plus_acct_data_cus,
613 0 : _ct->block_cost, _ct->block_cost_limit, _ct->account_cost_limit ));
614 0 : }
615 0 : txns[ i ].execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - (actual_execution_cus + actual_acct_data_cus);
616 0 : txns[ i ].execle_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus;
617 :
618 0 : txns[ i ].flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS | FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
619 0 : tips[ i ] = txn_out->details.tips;
620 :
621 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_LANDED_SUCCESS_IDX ]++;
622 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+FD_METRICS_ENUM_TRANSACTION_RESULT_V_SUCCESS_IDX ]++;
623 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_VERSION )+fd_execle_version_from_txn( TXN( &txns[ i ] ) ) ]++;
624 0 : }
625 0 : } else {
626 0 : FD_TEST( failed_idx != ULONG_MAX );
627 : /* A failed bundle is dropped in its entirety: every transaction is
628 : marked non-committable and none of them land in the block. We
629 : intentionally do NOT emit runtime_txn events here */
630 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
631 :
632 0 : ctx->txn_out[ i ].err.is_committable = 0;
633 :
634 0 : if( i>failed_idx ) {
635 0 : ctx->txn_out[ i ].details.load_start_ticks = LONG_MAX;
636 0 : ctx->txn_out[ i ].details.check_start_ticks = LONG_MAX;
637 0 : ctx->txn_out[ i ].details.exec_start_ticks = LONG_MAX;
638 0 : ctx->txn_out[ i ].details.commit_start_ticks = LONG_MAX;
639 0 : }
640 :
641 0 : uint requested_exec_plus_acct_data_cus = txns[ i ].pack_cu.requested_exec_plus_acct_data_cus;
642 0 : uint non_execution_cus = txns[ i ].pack_cu.non_execution_cus;
643 0 : txns[ i ].execle_cu.actual_consumed_cus = 0U;
644 0 : txns[ i ].execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
645 0 : tips[ i ] = 0UL;
646 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) );
647 :
648 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_LANDED )+FD_METRICS_ENUM_TRANSACTION_LANDED_V_UNLANDED_IDX ]++;
649 0 : if( i==failed_idx ) fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+(ulong)fd_execle_err_from_runtime_err( ctx->txn_out[ i ].err.txn_err ) ]++;
650 0 : else fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_RESULT )+FD_METRICS_ENUM_TRANSACTION_RESULT_V_BUNDLE_PEER_IDX ]++;
651 0 : fd_metrics_tl[ MIDX( COUNTER, EXECLE, TXN_VERSION )+fd_execle_version_from_txn( TXN( &txns[ i ] ) ) ]++;
652 0 : }
653 0 : }
654 :
655 0 : if( FD_LIKELY( setup_bundle ) ) fd_runtime_fini_bundle( ctx->runtime );
656 :
657 0 : if( FD_LIKELY( ctx->enable_rebates ) ) fd_pack_rebate_sum_add_txn( ctx->rebater, txns, writable_alt, txn_cnt );
658 :
659 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
660 0 : fd_txn_out_t const * txn_out = &ctx->txn_out[ i ];
661 :
662 0 : ctx->metrics.txn_load_cum_ticks += fd_ulong_if( txn_out->details.check_start_ticks==LONG_MAX || txn_out->details.load_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.check_start_ticks - txn_out->details.load_start_ticks ) );
663 0 : ctx->metrics.txn_check_cum_ticks += fd_ulong_if( txn_out->details.exec_start_ticks==LONG_MAX || txn_out->details.check_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.exec_start_ticks - txn_out->details.check_start_ticks ) );
664 0 : ctx->metrics.txn_exec_cum_ticks += fd_ulong_if( txn_out->details.commit_start_ticks==LONG_MAX || txn_out->details.exec_start_ticks==LONG_MAX, 0UL, (ulong)( txn_out->details.commit_start_ticks - txn_out->details.exec_start_ticks ) );
665 0 : ctx->metrics.txn_commit_cum_ticks += fd_ulong_if( txn_end_ticks[ i ]==LONG_MAX || txn_out->details.commit_start_ticks==LONG_MAX, 0UL, (ulong)( txn_end_ticks[ i ] - txn_out->details.commit_start_ticks ) );
666 0 : }
667 :
668 : /* Indicate to pack tile we are done processing the transactions so
669 : it can pack new microblocks using these accounts. */
670 0 : fd_fseq_update( ctx->busy_fseq, seq );
671 :
672 : /* We need to publish each transaction separately into its own
673 : microblock, so make a temporary copy on the stack so we can move
674 : all the data around. */
675 0 : fd_txn_p_t bundle_txn_temp[ 5UL ];
676 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
677 0 : bundle_txn_temp[ i ] = txns[ i ];
678 0 : }
679 :
680 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
681 0 : fd_txn_out_t * txn_out = &ctx->txn_out[ i ];
682 :
683 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_poh->mem, ctx->out_poh->chunk );
684 0 : fd_memcpy( dst, bundle_txn_temp+i, sizeof(fd_txn_p_t) );
685 :
686 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst+sizeof(fd_txn_p_t) );
687 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, 1UL, trailer->hash );
688 0 : trailer->pack_txn_idx = ctx->_txn_idx + i;
689 0 : trailer->tips = tips[ i ];
690 0 : trailer->bank_seq = bank->bank_seq;
691 0 : trailer->exec_start_ticks = bundle_start_ticks;
692 0 : trailer->exec_end_ticks = txn_end_ticks[ i ];
693 :
694 0 : ulong execle_sig = fd_disco_execle_sig( slot, ctx->_pack_idx+i );
695 :
696 0 : trailer->txn_ns_dt.load_start = fd_float_if( txn_out->details.load_start_ticks==LONG_MAX, 0., (float)fd_long_max( 0L, txn_out->details.load_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
697 0 : trailer->txn_ns_dt.check_start = fd_float_if( txn_out->details.check_start_ticks==LONG_MAX, trailer->txn_ns_dt.load_start, (float)fd_long_max( 0L, txn_out->details.check_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
698 0 : trailer->txn_ns_dt.exec_start = fd_float_if( txn_out->details.exec_start_ticks==LONG_MAX, trailer->txn_ns_dt.check_start, (float)fd_long_max( 0L, txn_out->details.exec_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
699 0 : trailer->txn_ns_dt.commit_start = fd_float_if( txn_out->details.commit_start_ticks==LONG_MAX, trailer->txn_ns_dt.exec_start, (float)fd_long_max( 0L, txn_out->details.commit_start_ticks - microblock_start_ticks ) * ctx->ns_per_tick );
700 0 : trailer->txn_ns_dt.commit_end = fd_float_if( txn_end_ticks[ i ]==LONG_MAX, trailer->txn_ns_dt.commit_start, (float)fd_long_max( 0L, txn_end_ticks[ i ] - microblock_start_ticks ) * ctx->ns_per_tick );
701 :
702 0 : ulong new_sz = sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
703 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( fd_tickcount() ) );
704 0 : ctx->out_poh->chunk = fd_dcache_compact_next( ctx->out_poh->chunk, new_sz, ctx->out_poh->chunk0, ctx->out_poh->wmark );
705 0 : }
706 0 : }
707 :
708 : static inline void
709 : after_frag( fd_execle_tile_t * ctx,
710 : ulong in_idx,
711 : ulong seq,
712 : ulong sig,
713 : ulong sz,
714 : ulong tsorig,
715 : ulong tspub,
716 0 : fd_stem_context_t * stem ) {
717 0 : (void)in_idx;
718 :
719 0 : ulong slot = fd_disco_poh_sig_slot( sig );
720 0 : if( FD_LIKELY( ctx->enable_rebates ) ) {
721 0 : if( FD_UNLIKELY( slot!=ctx->rebates_for_slot ) ) {
722 : /* If pack has already moved on to a new slot, the rebates are no
723 : longer useful. */
724 0 : fd_pack_rebate_sum_clear( ctx->rebater );
725 0 : ctx->rebate_microblock_cnt = 0UL;
726 0 : ctx->rebate_idle_loop_cnt = 0UL;
727 0 : ctx->rebate_draining = 0;
728 0 : ctx->rebates_for_slot = slot;
729 0 : }
730 0 : if( FD_UNLIKELY( !ctx->rebate_microblock_cnt ) ) {
731 0 : ctx->rebate_tsorig = tsorig;
732 0 : }
733 0 : ctx->rebate_idle_loop_cnt = 0UL;
734 0 : }
735 :
736 0 : if( FD_UNLIKELY( ctx->_is_bundle ) ) handle_bundle( ctx, seq, sig, sz, tspub, stem );
737 0 : else handle_microblock( ctx, seq, sig, sz, tspub, stem );
738 0 : if( FD_LIKELY( ctx->enable_rebates ) ) {
739 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
740 0 : ctx->rebate_microblock_cnt += fd_ulong_if( ctx->_is_bundle, txn_cnt, 1UL );
741 0 : }
742 :
743 : /* Return the pack_execle credit now rather than at housekeeping, so
744 : the link can be shallow enough for pack to keep it in cache. */
745 0 : fd_fseq_update( ctx->pack_in_fseq, seq+1UL );
746 0 : }
747 :
748 : static inline fd_execle_out_t
749 : out1( fd_topo_t const * topo,
750 : fd_topo_tile_t const * tile,
751 0 : char const * name ) {
752 0 : ulong idx = ULONG_MAX;
753 :
754 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
755 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
756 0 : if( !strcmp( link->name, name ) ) {
757 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 ));
758 0 : idx = i;
759 0 : }
760 0 : }
761 :
762 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_execle_out_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
763 :
764 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
765 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
766 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
767 :
768 0 : return (fd_execle_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
769 0 : }
770 :
771 : static void
772 : privileged_init( fd_topo_t const * topo,
773 0 : fd_topo_tile_t const * tile ) {
774 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
775 :
776 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
777 0 : fd_execle_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_execle_tile_t), sizeof(fd_execle_tile_t) );
778 0 : FD_TEST( fd_rng_secure( &ctx->rebate_seed, sizeof(ctx->rebate_seed) ) );
779 0 : }
780 :
781 : static void
782 : unprivileged_init( fd_topo_t const * topo,
783 0 : fd_topo_tile_t const * tile ) {
784 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
785 :
786 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
787 0 : fd_execle_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_execle_tile_t), sizeof(fd_execle_tile_t) );
788 0 : void * blake3 = FD_SCRATCH_ALLOC_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
789 0 : void * bmtree = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
790 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->execle.max_live_slots ) );
791 0 : void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->execle.max_live_slots ) );
792 0 : void * pc_scratch = FD_SCRATCH_ALLOC_APPEND( l, FD_PROGCACHE_SCRATCH_ALIGN, FD_PROGCACHE_SCRATCH_FOOTPRINT );
793 :
794 0 : #define NONNULL( x ) (__extension__({ \
795 0 : __typeof__((x)) __x = (x); \
796 0 : if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
797 0 : __x; }))
798 :
799 0 : ctx->kind_id = tile->kind_id;
800 0 : ctx->blake3 = NONNULL( fd_blake3_join( fd_blake3_new( blake3 ) ) );
801 0 : ctx->bmtree = NONNULL( bmtree );
802 :
803 0 : NONNULL( fd_pack_rebate_sum_join( fd_pack_rebate_sum_new( ctx->rebater, ctx->rebate_seed ) ) );
804 0 : ctx->rebates_for_slot = 0UL;
805 0 : ctx->rebate_microblock_cnt = 0UL;
806 0 : ctx->rebate_idle_loop_cnt = 0UL;
807 0 : ctx->rebate_draining = 0;
808 :
809 0 : FD_TEST( fd_progcache_join( ctx->progcache,
810 0 : fd_topo_obj_laddr( topo, tile->execle.progcache_obj_id ),
811 0 : pc_scratch, FD_PROGCACHE_SCRATCH_FOOTPRINT ) );
812 :
813 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->execle.txncache_obj_id );
814 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
815 0 : FD_TEST( txncache_shmem );
816 0 : fd_txncache_t * txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
817 0 : FD_TEST( txncache );
818 :
819 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->execle.accdb_obj_id );
820 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
821 0 : FD_TEST( accdb_shmem );
822 0 : ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL ) );
823 0 : FD_TEST( ctx->accdb );
824 :
825 0 : for( ulong i=0UL; i<FD_PACK_MAX_TXN_PER_BUNDLE; i++ ) {
826 0 : ctx->txn_in[ i ].bundle.prev_txn_cnt = i;
827 0 : for( ulong j=0UL; j<i; j++ ) ctx->txn_in[ i ].bundle.prev_txn_outs[ j ] = &ctx->txn_out[ j ];
828 0 : }
829 :
830 0 : ctx->runtime->accdb = ctx->accdb;
831 0 : ctx->runtime->progcache = ctx->progcache;
832 0 : ctx->runtime->status_cache = txncache;
833 0 : memset( &ctx->runtime->log, 0, sizeof(ctx->runtime->log) );
834 0 : ctx->runtime->log.log_collector = ctx->log_collector;
835 0 : ctx->runtime->fuzz.enabled = 0;
836 0 : ctx->runtime->fuzz.reclaim_accounts = 0;
837 0 : ctx->runtime->accounts.executable_cnt = 0UL;
838 0 : ctx->runtime->accounts.account_cnt = 0UL;
839 :
840 0 : ulong banks_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "banks" );
841 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
842 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
843 0 : NONNULL( ctx->banks );
844 :
845 0 : ulong busy_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "execle_busy.%lu", tile->kind_id );
846 0 : FD_TEST( busy_obj_id!=ULONG_MAX );
847 0 : ctx->busy_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, busy_obj_id ) );
848 0 : ctx->pack_in_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->in_link_fseq_obj_id[ 0UL ] ) );
849 0 : FD_TEST( ctx->pack_in_fseq );
850 0 : if( FD_UNLIKELY( !ctx->busy_fseq ) ) FD_LOG_ERR(( "execle tile %lu has no busy flag", tile->kind_id ));
851 :
852 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
853 0 : memset( &ctx->runtime->metrics, 0, sizeof( ctx->runtime->metrics ) );
854 :
855 0 : ctx->pack_in_mem = topo->workspaces[ topo->objs[ topo->links[ tile->in_link_id[ 0UL ] ].dcache_obj_id ].wksp_id ].wksp;
856 0 : ctx->pack_in_chunk0 = fd_dcache_compact_chunk0( ctx->pack_in_mem, topo->links[ tile->in_link_id[ 0UL ] ].dcache );
857 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 );
858 :
859 0 : *ctx->out_poh = out1( topo, tile, "execle_poh" ); FD_TEST( ctx->out_poh->idx!=ULONG_MAX );
860 0 : *ctx->out_pack = out1( topo, tile, "execle_pack" );
861 :
862 0 : ctx->enable_rebates = ctx->out_pack->idx!=ULONG_MAX;
863 :
864 0 : ctx->ns_per_tick = 1.f / (float)fd_tempo_tick_per_ns( NULL );
865 :
866 0 : fd_startup_gate_init( ctx->startup_gate, topo, tile->in_cnt );
867 :
868 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
869 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
870 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
871 0 : }
872 :
873 : static ulong
874 : populate_allowed_seccomp( fd_topo_t const * topo,
875 : fd_topo_tile_t const * tile,
876 : ulong out_cnt,
877 0 : struct sock_filter * out ) {
878 0 : (void)topo;
879 0 : (void)tile;
880 :
881 0 : populate_sock_filter_policy_fd_execle_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW );
882 0 : return sock_filter_policy_fd_execle_tile_instr_cnt;
883 0 : }
884 :
885 : static ulong
886 : populate_allowed_fds( fd_topo_t const * topo,
887 : fd_topo_tile_t const * tile,
888 : ulong out_fds_cnt,
889 0 : int * out_fds ) {
890 0 : (void)topo;
891 0 : (void)tile;
892 :
893 0 : if( FD_UNLIKELY( out_fds_cnt<3UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
894 :
895 0 : ulong out_cnt = 0UL;
896 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
897 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
898 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
899 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
900 :
901 0 : return out_cnt;
902 0 : }
903 :
904 : /* For a bundle, one bundle might burst into at most 5 separate PoH mixins, since the
905 : microblocks cannot be conflicting. */
906 :
907 0 : #define STEM_BURST (5UL)
908 :
909 : /* See explanation in fd_pack */
910 0 : #define STEM_LAZY (128L*3000L)
911 :
912 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_execle_tile_t
913 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_execle_tile_t)
914 :
915 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
916 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
917 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
918 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
919 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
920 :
921 : #include "../../disco/stem/fd_stem.c"
922 :
923 : static ulong
924 0 : max_event_sz( fd_topo_tile_t const * tile ) {
925 : /* execle emits accdb_partition_added, plus runtime_txn when diffs are on. */
926 0 : ulong sz = sizeof(fd_event_accdb_partition_added_t);
927 0 : if( tile->execle.report_runtime_diffs && sizeof(fd_event_runtime_txn_t)>sz ) sz = sizeof(fd_event_runtime_txn_t);
928 0 : return sz;
929 0 : }
930 :
931 : fd_topo_run_tile_t fd_tile_execle = {
932 : .name = "execle",
933 : .max_event_sz = max_event_sz,
934 : .populate_allowed_seccomp = populate_allowed_seccomp,
935 : .populate_allowed_fds = populate_allowed_fds,
936 : .scratch_align = scratch_align,
937 : .scratch_footprint = scratch_footprint,
938 : .privileged_init = privileged_init,
939 : .unprivileged_init = unprivileged_init,
940 : .run = stem_run,
941 : };
|