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