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