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