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