Line data Source code
1 : #include "fd_bank_abi.h"
2 :
3 : #include "../../disco/tiles.h"
4 : #include "../../disco/pack/fd_pack.h"
5 : #include "../../disco/pack/fd_pack_cost.h"
6 : #include "../../ballet/blake3/fd_blake3.h"
7 : #include "../../ballet/bmtree/fd_bmtree.h"
8 : #include "../../disco/metrics/fd_metrics.h"
9 : #include "../../util/pod/fd_pod_format.h"
10 : #include "../../disco/pack/fd_pack_rebate_sum.h"
11 : #include "../../disco/metrics/generated/fd_metrics_bank.h"
12 :
13 0 : #define FD_BANK_TRANSACTION_LANDED 1
14 : #define FD_BANK_TRANSACTION_EXECUTED 2
15 :
16 : typedef struct {
17 : ulong kind_id;
18 :
19 : fd_blake3_t * blake3;
20 : void * bmtree;
21 :
22 : uchar * txn_abi_mem;
23 : uchar * txn_sidecar_mem;
24 :
25 : void const * _bank;
26 : ulong _pack_idx;
27 : ulong _txn_idx;
28 : int _is_bundle;
29 : fd_acct_addr_t _alt_accts[MAX_TXN_PER_MICROBLOCK][FD_TXN_ACCT_ADDR_MAX];
30 :
31 : ulong * busy_fseq;
32 :
33 : fd_wksp_t * pack_in_mem;
34 : ulong pack_in_chunk0;
35 : ulong pack_in_wmark;
36 :
37 : fd_wksp_t * out_mem;
38 : ulong out_chunk0;
39 : ulong out_wmark;
40 : ulong out_chunk;
41 :
42 : fd_wksp_t * rebate_mem;
43 : ulong rebate_chunk0;
44 : ulong rebate_wmark;
45 : ulong rebate_chunk;
46 : ulong rebates_for_slot;
47 : fd_pack_rebate_sum_t rebater[ 1 ];
48 :
49 : struct {
50 : ulong txn_load_address_lookup_tables[ 6 ];
51 : ulong transaction_result[ 41 ];
52 : ulong processing_failed;
53 : ulong fee_only;
54 : ulong exec_failed;
55 : ulong success;
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_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT );
72 0 : l = FD_LAYOUT_APPEND( l, FD_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR_MAX );
73 0 : return FD_LAYOUT_FINI( l, scratch_align() );
74 0 : }
75 :
76 : static inline void
77 0 : metrics_write( fd_bank_ctx_t * ctx ) {
78 0 : FD_MCNT_ENUM_COPY( BANK, TRANSACTION_LOAD_ADDRESS_TABLES, ctx->metrics.txn_load_address_lookup_tables );
79 0 : FD_MCNT_ENUM_COPY( BANK, TRANSACTION_RESULT, ctx->metrics.transaction_result );
80 :
81 0 : FD_MCNT_SET( BANK, PROCESSING_FAILED, ctx->metrics.processing_failed );
82 0 : FD_MCNT_SET( BANK, FEE_ONLY_TRANSACTIONS, ctx->metrics.fee_only );
83 0 : FD_MCNT_SET( BANK, EXECUTED_FAILED_TRANSACTIONS, ctx->metrics.exec_failed );
84 0 : FD_MCNT_SET( BANK, SUCCESSFUL_TRANSACTIONS, ctx->metrics.success );
85 0 : }
86 :
87 : static int
88 : before_frag( fd_bank_ctx_t * ctx,
89 : ulong in_idx,
90 : ulong seq,
91 0 : ulong sig ) {
92 0 : (void)in_idx;
93 0 : (void)seq;
94 :
95 : /* Pack also outputs "leader slot done" which we can ignore. */
96 0 : if( FD_UNLIKELY( fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_MICROBLOCK ) ) return 1;
97 :
98 0 : ulong target_bank_idx = fd_disco_poh_sig_execle_tile( sig );
99 0 : if( FD_UNLIKELY( target_bank_idx!=ctx->kind_id ) ) return 1;
100 :
101 0 : return 0;
102 0 : }
103 :
104 : extern int fd_ext_bank_execute_and_commit_bundle( void const * bank, void * txns, ulong txn_cnt, int * out_transaction_err, uint * actual_execution_cus, uint * actual_acct_data_cus, ulong * out_timestamps, ulong * out_tips );
105 : extern void * fd_ext_bank_load_and_execute_txns( void const * bank, void * txns, ulong txn_cnt, int * out_processing_results, int * out_transaction_err, uint * out_consumed_exec_cus, uint * out_consumed_acct_data_cus, ulong * out_timestamps, ulong * out_tips );
106 : extern void fd_ext_bank_commit_txns( void const * bank, void const * txns, ulong txn_cnt , void * load_and_execute_output );
107 : extern void fd_ext_bank_release_thunks( void * load_and_execute_output );
108 :
109 : static inline void
110 : during_frag( fd_bank_ctx_t * ctx,
111 : ulong in_idx FD_PARAM_UNUSED,
112 : ulong seq FD_PARAM_UNUSED,
113 : ulong sig FD_PARAM_UNUSED,
114 : ulong chunk,
115 : ulong sz,
116 0 : ulong ctl FD_PARAM_UNUSED ) {
117 :
118 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->pack_in_mem, chunk );
119 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
120 :
121 0 : if( FD_UNLIKELY( chunk<ctx->pack_in_chunk0 || chunk>ctx->pack_in_wmark || sz>USHORT_MAX ) )
122 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->pack_in_chunk0, ctx->pack_in_wmark ));
123 :
124 : /* Pack sends fd_txn_e_t (with ALT accounts), but PoH expects fd_txn_p_t.
125 : We copy the fd_txn_p_t portion to the PoH output buffer, and copy the
126 : ALT accounts to the tile context for rebates. */
127 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
128 0 : fd_txn_e_t const * src_txn_e = (fd_txn_e_t const *)src;
129 0 : fd_txn_p_t * dst_txn_p = (fd_txn_p_t *)dst;
130 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
131 0 : fd_memcpy( dst_txn_p + i, src_txn_e[i].txnp, sizeof(fd_txn_p_t) );
132 0 : ulong alt_cnt = fd_ulong_min( (ulong)TXN(src_txn_e[i].txnp)->addr_table_adtl_cnt, FD_TXN_ACCT_ADDR_MAX );
133 0 : fd_memcpy( ctx->_alt_accts[i], src_txn_e[i].alt_accts, alt_cnt * sizeof(fd_acct_addr_t) );
134 0 : }
135 :
136 0 : fd_microblock_execle_trailer_t * trailer = (fd_microblock_execle_trailer_t *)( src+sz-sizeof(fd_microblock_execle_trailer_t) );
137 0 : ctx->_bank = trailer->bank;
138 0 : ctx->_pack_idx = trailer->pack_idx;
139 0 : ctx->_txn_idx = trailer->pack_txn_idx;
140 0 : ctx->_is_bundle = trailer->is_bundle;
141 0 : }
142 :
143 : static void
144 : hash_transactions( void * mem,
145 : fd_txn_p_t * txns,
146 : ulong txn_cnt,
147 0 : uchar * mixin ) {
148 0 : fd_bmtree_commit_t * bmtree = fd_bmtree_commit_init( mem, 32UL, 1UL, 0UL );
149 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
150 0 : fd_txn_p_t * _txn = txns + i;
151 0 : if( FD_UNLIKELY( !(_txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
152 :
153 0 : fd_txn_t * txn = TXN(_txn);
154 0 : for( ulong j=0; j<txn->signature_cnt; j++ ) {
155 0 : fd_bmtree_node_t node[1];
156 0 : fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+64UL*j, 64UL, 1UL );
157 0 : fd_bmtree_commit_append( bmtree, node, 1UL );
158 0 : }
159 0 : }
160 0 : uchar * root = fd_bmtree_commit_fini( bmtree );
161 0 : fd_memcpy( mixin, root, 32UL );
162 0 : }
163 :
164 : static inline void
165 : handle_microblock( fd_bank_ctx_t * ctx,
166 : ulong seq,
167 : ulong sig,
168 : ulong sz,
169 : ulong begin_tspub,
170 0 : fd_stem_context_t * stem ) {
171 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
172 :
173 0 : ulong slot = fd_disco_poh_sig_slot( sig );
174 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
175 :
176 : /* Use ALT accounts copied to context for rebates. These were resolved
177 : by resolv_tile and are needed because the LUT may be deactivated by
178 : the time we get here. Execution still uses the bank's current view
179 : of the LUT via fd_bank_abi_txn_init. */
180 0 : fd_acct_addr_t const * writable_alt[ MAX_TXN_PER_MICROBLOCK ];
181 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
182 0 : writable_alt[i] = ctx->_alt_accts[i];
183 0 : }
184 :
185 0 : ulong sanitized_txn_cnt = 0UL;
186 0 : ulong sidecar_footprint_bytes = 0UL;
187 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
188 0 : fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );
189 :
190 0 : void * abi_txn = ctx->txn_abi_mem + (sanitized_txn_cnt*FD_BANK_ABI_TXN_FOOTPRINT);
191 0 : void * abi_txn_sidecar = ctx->txn_sidecar_mem + sidecar_footprint_bytes;
192 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
193 :
194 0 : int result = fd_bank_abi_txn_init( abi_txn, abi_txn_sidecar, ctx->_bank, slot, ctx->blake3, txn->payload, txn->payload_sz, TXN(txn), !!(txn->flags & FD_TXN_P_FLAGS_IS_SIMPLE_VOTE) );
195 0 : ctx->metrics.txn_load_address_lookup_tables[ (ulong)((long)FD_METRICS_COUNTER_BANK_TRANSACTION_LOAD_ADDRESS_TABLES_CNT+result-1L) ]++;
196 0 : if( FD_UNLIKELY( result!=FD_BANK_ABI_TXN_INIT_SUCCESS ) ) continue;
197 :
198 0 : txn->flags |= FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
199 :
200 0 : fd_txn_t * txn1 = TXN(txn);
201 0 : sidecar_footprint_bytes += FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR( txn1->acct_addr_cnt, txn1->addr_table_adtl_cnt, txn1->instr_cnt, txn1->addr_table_lookup_cnt );
202 0 : sanitized_txn_cnt++;
203 0 : }
204 :
205 : /* Just because a transaction was executed doesn't mean it succeeded,
206 : but all executed transactions get committed. */
207 0 : int processing_results [ MAX_TXN_PER_MICROBLOCK ] = { 0 };
208 0 : int transaction_err [ MAX_TXN_PER_MICROBLOCK ] = { 0 };
209 0 : uint consumed_exec_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
210 0 : uint consumed_acct_data_cus[ MAX_TXN_PER_MICROBLOCK ] = { 0U };
211 0 : ulong out_timestamps [ 4*MAX_TXN_PER_MICROBLOCK ] = { 0U };
212 0 : ulong out_tips [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
213 :
214 0 : void * load_and_execute_output = fd_ext_bank_load_and_execute_txns( ctx->_bank,
215 0 : ctx->txn_abi_mem,
216 0 : sanitized_txn_cnt,
217 0 : processing_results,
218 0 : transaction_err,
219 0 : consumed_exec_cus,
220 0 : consumed_acct_data_cus,
221 0 : out_timestamps,
222 0 : out_tips );
223 :
224 0 : ulong sanitized_idx = 0UL;
225 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
226 0 : fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );
227 :
228 0 : uint requested_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
229 0 : uint non_execution_cus = txn->pack_cu.non_execution_cus;
230 :
231 : /* Assume failure, set below if success. If it doesn't land cin the
232 : block, rebate the non-execution CUs too. */
233 0 : txn->execle_cu.actual_consumed_cus = 0U;
234 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
235 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
236 0 : if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue;
237 :
238 0 : sanitized_idx++;
239 :
240 : /* Stash the result in the flags value so that pack can inspect it. */
241 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)transaction_err[ sanitized_idx-1UL ]<<24);
242 :
243 0 : ctx->metrics.transaction_result[ transaction_err [ sanitized_idx-1UL ] ]++;
244 :
245 0 : ctx->metrics.processing_failed += (ulong)(processing_results[ sanitized_idx-1UL ]==0 );
246 0 : ctx->metrics.fee_only += (ulong)(processing_results[ sanitized_idx-1UL ]==FD_BANK_TRANSACTION_LANDED);
247 :
248 0 : if( FD_UNLIKELY( !(processing_results[ sanitized_idx-1UL ] & FD_BANK_TRANSACTION_LANDED) ) ) continue;
249 :
250 0 : uint actual_execution_cus = consumed_exec_cus[ sanitized_idx-1UL ];
251 0 : uint actual_acct_data_cus = consumed_acct_data_cus[ sanitized_idx-1UL ];
252 :
253 0 : int is_simple_vote = 0;
254 0 : if( FD_UNLIKELY( is_simple_vote = fd_txn_is_simple_vote_transaction( TXN(txn), txn->payload ) ) ) {
255 : /* Simple votes are charged fixed amounts of compute regardless of
256 : the real cost they incur. fd_ext_bank_load_and_execute_txns
257 : returns the real cost, however, so we override it here. */
258 0 : actual_execution_cus = FD_PACK_VOTE_DEFAULT_COMPUTE_UNITS;
259 0 : actual_acct_data_cus = 0U;
260 0 : }
261 :
262 : /* FeesOnly transactions are transactions that failed to load
263 : before they even reach the VM stage. They have zero execution
264 : cost but do charge for the account data they are able to load.
265 : FeesOnly votes are charged the fixed voe cost. */
266 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - ( actual_execution_cus + actual_acct_data_cus );
267 0 : txn->execle_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus;
268 :
269 : /* TXN_P_FLAGS_EXECUTE_SUCCESS means that it should be included in
270 : the block. It's a bit of a misnomer now that there are fee-only
271 : transactions. */
272 0 : txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
273 :
274 0 : if( FD_UNLIKELY( !(processing_results[ sanitized_idx-1UL ] & FD_BANK_TRANSACTION_EXECUTED) ) ) continue;
275 :
276 0 : if( transaction_err[ sanitized_idx-1UL ] ) ctx->metrics.exec_failed++;
277 0 : else ctx->metrics.success++;
278 :
279 : /* The VM will stop executing and fail an instruction immediately if
280 : it exceeds its requested CUs. A transaction which requests less
281 : account data than it actually consumes will fail in the account
282 : loading stage. */
283 0 : if( FD_UNLIKELY( actual_execution_cus+actual_acct_data_cus > requested_exec_plus_acct_data_cus ) ) {
284 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
285 0 : FD_LOG_ERR(( "Actual CUs unexpectedly exceeded requested amount. actual_execution_cus (%u) actual_acct_data_cus "
286 0 : "(%u) requested_exec_plus_acct_data_cus (%u) is_simple_vote (%i) exec_failed (%i)",
287 0 : actual_execution_cus, actual_acct_data_cus, requested_exec_plus_acct_data_cus, is_simple_vote,
288 0 : transaction_err[ sanitized_idx-1UL ] ));
289 0 : }
290 0 : }
291 :
292 : /* Commit must succeed so no failure path. This function takes
293 : ownership of the load_and_execute_output and will free it
294 : before it returns. They should not be reused. Once commit
295 : is called, the transactions MUST be mixed into the PoH otherwise
296 : we will fork and diverge, so the link from here til PoH mixin
297 : must be completely reliable with nothing dropped. */
298 0 : fd_ext_bank_commit_txns( ctx->_bank, ctx->txn_abi_mem, sanitized_txn_cnt, load_and_execute_output );
299 0 : load_and_execute_output = NULL;
300 :
301 : /* Indicate to pack tile we are done processing the transactions so
302 : it can pack new microblocks using these accounts. */
303 0 : fd_fseq_update( ctx->busy_fseq, seq );
304 :
305 : /* Prepare the rebate */
306 0 : fd_pack_rebate_sum_add_txn( ctx->rebater, (fd_txn_p_t const *)dst, writable_alt, txn_cnt );
307 :
308 : /* Now produce the merkle hash of the transactions for inclusion
309 : (mixin) to the PoH hash. This is done on the bank tile because
310 : it shards / scales horizontally here, while PoH does not. */
311 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst + txn_cnt*sizeof(fd_txn_p_t) );
312 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, txn_cnt, trailer->hash );
313 0 : trailer->pack_txn_idx = ctx->_txn_idx;
314 0 : trailer->tips = 0;
315 0 : for( ulong i=0UL; i<txn_cnt; i++ ) trailer->tips += out_tips[ i ];
316 :
317 0 : long tickcount = fd_tickcount();
318 0 : long microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, tickcount );
319 0 : long microblock_duration_ticks = fd_long_max(tickcount - microblock_start_ticks, 0L);
320 :
321 0 : long tx_start_ticks = (long)out_timestamps[ 0 ];
322 0 : long tx_load_end_ticks = (long)out_timestamps[ 1 ];
323 0 : long tx_end_ticks = (long)out_timestamps[ 2 ];
324 0 : long tx_preload_end_ticks = (long)out_timestamps[ 3 ];
325 :
326 0 : trailer->txn_start_pct = (uchar)(((double)(tx_start_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
327 0 : trailer->txn_load_end_pct = (uchar)(((double)(tx_load_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
328 0 : trailer->txn_end_pct = (uchar)(((double)(tx_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
329 0 : trailer->txn_preload_end_pct = (uchar)(((double)(tx_preload_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
330 :
331 : /* When sending MAX_TXN_PER_MICROBLOCK transactions as fd_txn_p_t to PoH,
332 : there's always extra bytes at the end to stash the trailer. */
333 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_trailer_t), poh_shred_mtu );
334 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_execle_trailer_t), poh_shred_mtu );
335 :
336 : /* We have a race window with the GUI, where if the slot is ending it
337 : will snap these metrics to draw the waterfall, but see them outdated
338 : because housekeeping hasn't run. For now just update them here, but
339 : PoH should eventually flush the pipeline before ending the slot. */
340 0 : metrics_write( ctx );
341 :
342 0 : ulong bank_sig = fd_disco_execle_sig( slot, ctx->_pack_idx );
343 :
344 : /* We always need to publish, even if there are no successfully executed
345 : transactions so the PoH tile can keep an accurate count of microblocks
346 : it has seen. */
347 0 : ulong new_sz = txn_cnt*sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
348 0 : fd_stem_publish( stem, 0UL, bank_sig, ctx->out_chunk, new_sz, 0UL, 0UL, (ulong)fd_frag_meta_ts_comp( tickcount ) );
349 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, new_sz, ctx->out_chunk0, ctx->out_wmark );
350 0 : }
351 :
352 : static inline void
353 : handle_bundle( fd_bank_ctx_t * ctx,
354 : ulong seq,
355 : ulong sig,
356 : ulong sz,
357 : ulong begin_tspub,
358 0 : fd_stem_context_t * stem ) {
359 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
360 0 : fd_txn_p_t * txns = (fd_txn_p_t *)dst;
361 :
362 0 : ulong slot = fd_disco_poh_sig_slot( sig );
363 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_execle_trailer_t))/sizeof(fd_txn_e_t);
364 :
365 : /* Use ALT accounts copied to context for rebates. These were resolved
366 : by resolv_tile and are needed because the LUT may be deactivated by
367 : the time we get here. Execution still uses the bank's current view
368 : of the LUT via fd_bank_abi_txn_init. */
369 0 : fd_acct_addr_t const * writable_alt[ MAX_TXN_PER_MICROBLOCK ];
370 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
371 0 : writable_alt[i] = ctx->_alt_accts[i];
372 0 : }
373 :
374 0 : int execution_success = 1;
375 :
376 0 : ulong sidecar_footprint_bytes = 0UL;
377 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
378 0 : fd_txn_p_t * txn = txns+i;
379 :
380 0 : void * abi_txn = ctx->txn_abi_mem + (i*FD_BANK_ABI_TXN_FOOTPRINT);
381 0 : void * abi_txn_sidecar = ctx->txn_sidecar_mem + sidecar_footprint_bytes;
382 0 : txn->flags &= ~(FD_TXN_P_FLAGS_SANITIZE_SUCCESS | FD_TXN_P_FLAGS_EXECUTE_SUCCESS);
383 :
384 0 : int result = fd_bank_abi_txn_init( abi_txn, abi_txn_sidecar, ctx->_bank, slot, ctx->blake3, txn->payload, txn->payload_sz, TXN(txn), !!(txn->flags & FD_TXN_P_FLAGS_IS_SIMPLE_VOTE) );
385 0 : ctx->metrics.txn_load_address_lookup_tables[ (ulong)((long)FD_METRICS_COUNTER_BANK_TRANSACTION_LOAD_ADDRESS_TABLES_CNT+result-1L) ]++;
386 0 : if( FD_UNLIKELY( result!=FD_BANK_ABI_TXN_INIT_SUCCESS ) ) {
387 0 : execution_success = 0;
388 0 : continue;
389 0 : }
390 :
391 0 : txn->flags |= FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
392 :
393 0 : fd_txn_t * txn1 = TXN(txn);
394 0 : sidecar_footprint_bytes += FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR( txn1->acct_addr_cnt, txn1->addr_table_adtl_cnt, txn1->instr_cnt, txn1->addr_table_lookup_cnt );
395 0 : }
396 :
397 0 : int transaction_err [ MAX_TXN_PER_MICROBLOCK ];
398 0 : for( ulong i=0UL; i<txn_cnt; i++ ) transaction_err[ i ] = FD_METRICS_ENUM_TRANSACTION_ERROR_V_BUNDLE_PEER_IDX;
399 :
400 0 : uint actual_execution_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
401 0 : uint actual_acct_data_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
402 0 : uint consumed_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
403 0 : ulong out_timestamps [ 4*MAX_TXN_PER_MICROBLOCK ] = { 0U };
404 0 : ulong tips [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
405 0 : if( FD_LIKELY( execution_success ) ) {
406 0 : execution_success = fd_ext_bank_execute_and_commit_bundle( ctx->_bank, ctx->txn_abi_mem, txn_cnt, transaction_err, actual_execution_cus, actual_acct_data_cus, out_timestamps, tips );
407 0 : }
408 :
409 0 : if( FD_LIKELY( execution_success ) ) {
410 0 : ctx->metrics.success += txn_cnt;
411 0 : ctx->metrics.transaction_result[ FD_METRICS_ENUM_TRANSACTION_ERROR_V_SUCCESS_IDX ] += txn_cnt;
412 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
413 0 : txns[ i ].flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
414 0 : txns[ i ].flags = (txns[ i ].flags & 0x00FFFFFFU); /* Clear error bits to indicate success */
415 0 : }
416 0 : } else {
417 : /* If any transaction fails in a bundle ... they all fail */
418 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
419 0 : fd_txn_p_t * txn = txns+i;
420 :
421 : /* Don't double count metrics for transactions that failed to
422 : sanitize. */
423 0 : if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue;
424 :
425 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
426 0 : txn->flags = (txn->flags & 0x00FFFFFFU) | ((uint)transaction_err[ i ]<<24);
427 0 : ctx->metrics.processing_failed++;
428 0 : ctx->metrics.transaction_result[ transaction_err[ i ] ]++;
429 0 : }
430 0 : }
431 :
432 : /* Indicate to pack tile we are done processing the transactions so
433 : it can pack new microblocks using these accounts. */
434 0 : fd_fseq_update( ctx->busy_fseq, seq );
435 :
436 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
437 0 : fd_txn_p_t * txn = txns+i;
438 :
439 0 : uint requested_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
440 0 : uint non_execution_cus = txn->pack_cu.non_execution_cus;
441 :
442 0 : if( FD_UNLIKELY( fd_txn_is_simple_vote_transaction( TXN(txns + i), txns[ i ].payload ) ) ) {
443 : /* Although bundles dont typically contain simple votes, we want
444 : to charge them correctly anyways. */
445 0 : consumed_cus[ i ] = FD_PACK_VOTE_DEFAULT_COMPUTE_UNITS;
446 0 : } else {
447 : /* Note that some transactions will have 0 consumed cus because
448 : they were never actually executed, due to an earlier
449 : transaction failing. */
450 0 : consumed_cus[ i ] = actual_execution_cus[ i ] + actual_acct_data_cus[ i ];
451 0 : }
452 :
453 : /* Assume failure, set below if success. If it doesn't land in the
454 : block, rebate the non-execution CUs too. */
455 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;
456 :
457 : /* We want to include consumed CUs for failed bundles for
458 : monitoring, even though they aren't included in the block. This
459 : is safe because the poh tile first checks if a txn is included in
460 : the block before counting its "actual_consumed_cus" towards the
461 : block tally. */
462 0 : txn->execle_cu.actual_consumed_cus = non_execution_cus + consumed_cus[ i ];
463 :
464 0 : if( FD_LIKELY( execution_success ) ) {
465 0 : if( FD_UNLIKELY( consumed_cus[ i ] > requested_exec_plus_acct_data_cus ) ) {
466 0 : FD_LOG_HEXDUMP_WARNING(( "txn", txn->payload, txn->payload_sz ));
467 0 : FD_LOG_ERR(( "transaction %lu in bundle consumed %u CUs > requested %u CUs", i, consumed_cus[ i ], requested_exec_plus_acct_data_cus ));
468 0 : }
469 :
470 0 : txn->execle_cu.actual_consumed_cus = non_execution_cus + consumed_cus[ i ];
471 0 : txn->execle_cu.rebated_cus = requested_exec_plus_acct_data_cus - consumed_cus[ i ];
472 0 : }
473 0 : }
474 :
475 0 : fd_pack_rebate_sum_add_txn( ctx->rebater, txns, writable_alt, txn_cnt );
476 :
477 : /* We need to publish each transaction separately into its own
478 : microblock, so make a temporary copy on the stack so we can move
479 : all the data around. */
480 0 : fd_txn_p_t bundle_txn_temp[ 5UL ];
481 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
482 0 : bundle_txn_temp[ i ] = txns[ i ];
483 0 : }
484 :
485 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
486 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
487 0 : fd_memcpy( dst, bundle_txn_temp+i, sizeof(fd_txn_p_t) );
488 :
489 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst+sizeof(fd_txn_p_t) );
490 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, 1UL, trailer->hash );
491 0 : trailer->pack_txn_idx = ctx->_txn_idx + i;
492 0 : trailer->tips = tips[ i ];
493 :
494 0 : ulong bank_sig = fd_disco_execle_sig( slot, ctx->_pack_idx+i );
495 :
496 0 : long tickcount = fd_tickcount();
497 0 : long microblock_start_ticks = fd_frag_meta_ts_decomp( begin_tspub, tickcount );
498 0 : long microblock_duration_ticks = fd_long_max(tickcount - microblock_start_ticks, 0L);
499 :
500 0 : long tx_start_ticks = (long)out_timestamps[ 4*i + 0 ];
501 0 : long tx_load_end_ticks = (long)out_timestamps[ 4*i + 1 ];
502 0 : long tx_end_ticks = (long)out_timestamps[ 4*i + 2 ];
503 0 : long tx_preload_end_ticks = (long)out_timestamps[ 4*i + 3 ];
504 :
505 0 : trailer->txn_start_pct = (uchar)(((double)(tx_start_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
506 0 : trailer->txn_load_end_pct = (uchar)(((double)(tx_load_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
507 0 : trailer->txn_end_pct = (uchar)(((double)(tx_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
508 0 : trailer->txn_preload_end_pct = (uchar)(((double)(tx_preload_end_ticks - microblock_start_ticks) * (double)UCHAR_MAX) / (double)microblock_duration_ticks);
509 :
510 0 : ulong new_sz = sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
511 0 : fd_stem_publish( stem, 0UL, bank_sig, ctx->out_chunk, new_sz, 0UL, 0UL, (ulong)fd_frag_meta_ts_comp( tickcount ) );
512 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, new_sz, ctx->out_chunk0, ctx->out_wmark );
513 0 : }
514 :
515 0 : metrics_write( ctx );
516 0 : }
517 :
518 : static inline void
519 : after_frag( fd_bank_ctx_t * ctx,
520 : ulong in_idx,
521 : ulong seq,
522 : ulong sig,
523 : ulong sz,
524 : ulong tsorig,
525 : ulong tspub,
526 0 : fd_stem_context_t * stem ) {
527 0 : (void)in_idx;
528 :
529 0 : ulong slot = fd_disco_poh_sig_slot( sig );
530 0 : if( FD_UNLIKELY( slot!=ctx->rebates_for_slot ) ) {
531 : /* If pack has already moved on to a new slot, the rebates are no
532 : longer useful. */
533 0 : fd_pack_rebate_sum_clear( ctx->rebater );
534 0 : ctx->rebates_for_slot = slot;
535 0 : }
536 :
537 0 : if( FD_UNLIKELY( ctx->_is_bundle ) ) handle_bundle( ctx, seq, sig, sz, tspub, stem );
538 0 : else handle_microblock( ctx, seq, sig, sz, tspub, stem );
539 :
540 : /* TODO: Use fancier logic to coalesce rebates e.g. and move this to
541 : after_credit */
542 0 : ulong written_sz = 0UL;
543 0 : while( 0UL!=(written_sz=fd_pack_rebate_sum_report( ctx->rebater, fd_chunk_to_laddr( ctx->rebate_mem, ctx->rebate_chunk ) )) ) {
544 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
545 0 : fd_stem_publish( stem, 1UL, slot, ctx->rebate_chunk, written_sz, 0UL, tsorig, tspub );
546 0 : ctx->rebate_chunk = fd_dcache_compact_next( ctx->rebate_chunk, written_sz, ctx->rebate_chunk0, ctx->rebate_wmark );
547 0 : }
548 0 : }
549 :
550 : static void
551 : unprivileged_init( fd_topo_t * topo,
552 0 : fd_topo_tile_t * tile ) {
553 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
554 :
555 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
556 0 : fd_bank_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_bank_ctx_t ), sizeof( fd_bank_ctx_t ) );
557 0 : void * blake3 = FD_SCRATCH_ALLOC_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
558 0 : void * bmtree = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
559 0 : ctx->txn_abi_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT );
560 0 : ctx->txn_sidecar_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR_MAX );
561 :
562 0 : #define NONNULL( x ) (__extension__({ \
563 0 : __typeof__((x)) __x = (x); \
564 0 : if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
565 0 : __x; }))
566 :
567 0 : ctx->kind_id = tile->kind_id;
568 0 : ctx->blake3 = NONNULL( fd_blake3_join( fd_blake3_new( blake3 ) ) );
569 0 : ctx->bmtree = NONNULL( bmtree );
570 :
571 0 : NONNULL( fd_pack_rebate_sum_join( fd_pack_rebate_sum_new( ctx->rebater ) ) );
572 0 : ctx->rebates_for_slot = 0UL;
573 :
574 0 : ulong busy_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "execle_busy.%lu", tile->kind_id );
575 0 : FD_TEST( busy_obj_id!=ULONG_MAX );
576 0 : ctx->busy_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, busy_obj_id ) );
577 0 : if( FD_UNLIKELY( !ctx->busy_fseq ) ) FD_LOG_ERR(( "banking tile %lu has no busy flag", tile->kind_id ));
578 :
579 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
580 :
581 0 : ctx->pack_in_mem = topo->workspaces[ topo->objs[ topo->links[ tile->in_link_id[ 0UL ] ].dcache_obj_id ].wksp_id ].wksp;
582 0 : ctx->pack_in_chunk0 = fd_dcache_compact_chunk0( ctx->pack_in_mem, topo->links[ tile->in_link_id[ 0UL ] ].dcache );
583 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 );
584 :
585 0 : ctx->out_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
586 0 : ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
587 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 );
588 0 : ctx->out_chunk = ctx->out_chunk0;
589 :
590 :
591 0 : ctx->rebate_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 1 ] ].dcache_obj_id ].wksp_id ].wksp;
592 0 : ctx->rebate_chunk0 = fd_dcache_compact_chunk0( ctx->rebate_mem, topo->links[ tile->out_link_id[ 1 ] ].dcache );
593 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 );
594 0 : ctx->rebate_chunk = ctx->rebate_chunk0;
595 0 : }
596 :
597 : /* For a bundle, one bundle might burst into at most 5 separate PoH mixins, since the
598 : microblocks cannot be conflicting. */
599 :
600 0 : #define STEM_BURST (5UL)
601 :
602 : /* See explanation in fd_pack */
603 0 : #define STEM_LAZY (128L*3000L)
604 :
605 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_bank_ctx_t
606 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_bank_ctx_t)
607 :
608 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
609 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
610 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
611 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
612 :
613 : #include "../../disco/stem/fd_stem.c"
614 :
615 : fd_topo_run_tile_t fd_tile_bank = {
616 : .name = "bank",
617 : .scratch_align = scratch_align,
618 : .scratch_footprint = scratch_footprint,
619 : .unprivileged_init = unprivileged_init,
620 : .run = stem_run,
621 : };
|