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