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