Line data Source code
1 : #include "../../../../disco/tiles.h"
2 :
3 : #include "../../../../ballet/pack/fd_pack.h"
4 : #include "../../../../ballet/blake3/fd_blake3.h"
5 : #include "../../../../ballet/bmtree/fd_bmtree.h"
6 : #include "../../../../disco/metrics/fd_metrics.h"
7 : #include "../../../../disco/topo/fd_pod_format.h"
8 : #include "../../../../disco/bank/fd_bank_abi.h"
9 : #include "../../../../disco/metrics/generated/fd_metrics_bank.h"
10 :
11 : typedef struct {
12 : ulong kind_id;
13 :
14 : fd_blake3_t * blake3;
15 : void * bmtree;
16 :
17 : uchar * txn_abi_mem;
18 : uchar * txn_sidecar_mem;
19 :
20 : void const * _bank;
21 : ulong _microblock_idx;
22 :
23 : ulong * busy_fseq;
24 :
25 : fd_wksp_t * pack_in_mem;
26 : ulong pack_in_chunk0;
27 : ulong pack_in_wmark;
28 :
29 : fd_wksp_t * out_mem;
30 : ulong out_chunk0;
31 : ulong out_wmark;
32 : ulong out_chunk;
33 :
34 : struct {
35 : ulong slot_acquire[ 3 ];
36 :
37 : ulong txn_load_address_lookup_tables[ 6 ];
38 : ulong txn_load[ 39 ];
39 : ulong txn_executing[ 39 ];
40 : ulong txn_executed[ 39 ];
41 : } metrics;
42 : } fd_bank_ctx_t;
43 :
44 : FD_FN_CONST static inline ulong
45 6 : scratch_align( void ) {
46 6 : return 128UL;
47 6 : }
48 :
49 : FD_FN_PURE static inline ulong
50 6 : scratch_footprint( fd_topo_tile_t const * tile ) {
51 6 : (void)tile;
52 6 : ulong l = FD_LAYOUT_INIT;
53 6 : l = FD_LAYOUT_APPEND( l, alignof( fd_bank_ctx_t ), sizeof( fd_bank_ctx_t ) );
54 6 : l = FD_LAYOUT_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
55 6 : l = FD_LAYOUT_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
56 6 : l = FD_LAYOUT_APPEND( l, FD_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT );
57 6 : l = FD_LAYOUT_APPEND( l, FD_BANK_ABI_TXN_ALIGN, FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR_MAX );
58 6 : return FD_LAYOUT_FINI( l, scratch_align() );
59 6 : }
60 :
61 : static inline void
62 0 : metrics_write( fd_bank_ctx_t * ctx ) {
63 0 : FD_MCNT_ENUM_COPY( BANK_TILE, SLOT_ACQUIRE, ctx->metrics.slot_acquire );
64 :
65 0 : FD_MCNT_ENUM_COPY( BANK_TILE, TRANSACTION_LOAD_ADDRESS_TABLES, ctx->metrics.txn_load_address_lookup_tables );
66 0 : FD_MCNT_ENUM_COPY( BANK_TILE, TRANSACTION_LOAD, ctx->metrics.txn_load );
67 0 : FD_MCNT_ENUM_COPY( BANK_TILE, TRANSACTION_EXECUTING, ctx->metrics.txn_executing );
68 0 : FD_MCNT_ENUM_COPY( BANK_TILE, TRANSACTION_EXECUTED, ctx->metrics.txn_executed );
69 :
70 0 : }
71 :
72 : static int
73 : before_frag( fd_bank_ctx_t * ctx,
74 : ulong in_idx,
75 : ulong seq,
76 0 : ulong sig ) {
77 0 : (void)in_idx;
78 0 : (void)seq;
79 :
80 : /* Pack also outputs "leader slot done" which we can ignore. */
81 0 : if( FD_UNLIKELY( fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_MICROBLOCK ) ) return 1;
82 :
83 0 : ulong target_bank_idx = fd_disco_poh_sig_bank_tile( sig );
84 0 : if( FD_UNLIKELY( target_bank_idx!=ctx->kind_id ) ) return 1;
85 :
86 0 : return 0;
87 0 : }
88 :
89 : extern void * fd_ext_bank_pre_balance_info( void const * bank, void * txns, ulong txn_cnt );
90 : extern void * fd_ext_bank_load_and_execute_txns( void const * bank, void * txns, ulong txn_cnt, int * out_load_results, int * out_executing_results, int * out_executed_results, uint * out_consumed_cus );
91 : extern void fd_ext_bank_commit_txns( void const * bank, void const * txns, ulong txn_cnt , void * load_and_execute_output, void * pre_balance_info );
92 : extern void fd_ext_bank_release_thunks( void * load_and_execute_output );
93 : extern void fd_ext_bank_release_pre_balance_info( void * pre_balance_info );
94 : extern int fd_ext_bank_verify_precompiles( void const * bank, void const * txn );
95 :
96 : static inline void
97 : during_frag( fd_bank_ctx_t * ctx,
98 : ulong in_idx,
99 : ulong seq,
100 : ulong sig,
101 : ulong chunk,
102 0 : ulong sz ) {
103 0 : (void)in_idx;
104 0 : (void)seq;
105 0 : (void)sig;
106 :
107 0 : uchar * src = (uchar *)fd_chunk_to_laddr( ctx->pack_in_mem, chunk );
108 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
109 :
110 0 : if( FD_UNLIKELY( chunk<ctx->pack_in_chunk0 || chunk>ctx->pack_in_wmark || sz>USHORT_MAX ) )
111 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->pack_in_chunk0, ctx->pack_in_wmark ));
112 :
113 0 : fd_memcpy( dst, src, sz-sizeof(fd_microblock_bank_trailer_t) );
114 0 : fd_microblock_bank_trailer_t * trailer = (fd_microblock_bank_trailer_t *)( src+sz-sizeof(fd_microblock_bank_trailer_t) );
115 0 : ctx->_bank = trailer->bank;
116 0 : ctx->_microblock_idx = trailer->microblock_idx;
117 0 : }
118 :
119 : static void
120 : hash_transactions( void * mem,
121 : fd_txn_p_t * txns,
122 : ulong txn_cnt,
123 0 : uchar * mixin ) {
124 0 : fd_bmtree_commit_t * bmtree = fd_bmtree_commit_init( mem, 32UL, 1UL, 0UL );
125 0 : for( ulong i=0; i<txn_cnt; i++ ) {
126 0 : fd_txn_p_t * _txn = txns + i;
127 0 : if( FD_UNLIKELY( !(_txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
128 :
129 0 : fd_txn_t * txn = TXN(_txn);
130 0 : for( ulong j=0; j<txn->signature_cnt; j++ ) {
131 0 : fd_bmtree_node_t node[1];
132 0 : fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+64UL*j, 64UL, 1UL );
133 0 : fd_bmtree_commit_append( bmtree, node, 1UL );
134 0 : }
135 0 : }
136 0 : uchar * root = fd_bmtree_commit_fini( bmtree );
137 0 : fd_memcpy( mixin, root, 32UL );
138 0 : }
139 :
140 : static inline void
141 : after_frag( fd_bank_ctx_t * ctx,
142 : ulong in_idx,
143 : ulong seq,
144 : ulong sig,
145 : ulong chunk,
146 : ulong sz,
147 : ulong tsorig,
148 0 : fd_stem_context_t * stem ) {
149 0 : (void)in_idx;
150 0 : (void)chunk;
151 0 : (void)tsorig;
152 :
153 0 : uchar * dst = (uchar *)fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
154 :
155 0 : ulong slot = fd_disco_poh_sig_slot( sig );
156 0 : ulong txn_cnt = (sz-sizeof(fd_microblock_bank_trailer_t))/sizeof(fd_txn_p_t);
157 :
158 0 : ulong sanitized_txn_cnt = 0UL;
159 0 : ulong sidecar_footprint_bytes = 0UL;
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 :
163 0 : void * abi_txn = ctx->txn_abi_mem + (sanitized_txn_cnt*FD_BANK_ABI_TXN_FOOTPRINT);
164 0 : void * abi_txn_sidecar = ctx->txn_sidecar_mem + sidecar_footprint_bytes;
165 0 : txn->flags &= ~FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
166 :
167 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) );
168 0 : ctx->metrics.txn_load_address_lookup_tables[ result ]++;
169 0 : if( FD_UNLIKELY( result!=FD_BANK_ABI_TXN_INIT_SUCCESS ) ) continue;
170 :
171 0 : int precompile_result = fd_ext_bank_verify_precompiles( ctx->_bank, abi_txn );
172 0 : if( FD_UNLIKELY( precompile_result ) ) {
173 0 : FD_MCNT_INC( BANK_TILE, PRECOMPILE_VERIFY_FAILURE, 1 );
174 0 : continue;
175 0 : }
176 :
177 0 : txn->flags |= FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
178 :
179 0 : fd_txn_t * txn1 = TXN(txn);
180 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 );
181 0 : sanitized_txn_cnt++;
182 0 : }
183 :
184 : /* Just because a transaction was executed doesn't mean it succeeded,
185 : but all executed transactions get committed. */
186 0 : int load_results [ MAX_TXN_PER_MICROBLOCK ] = { 0 };
187 0 : int executing_results[ MAX_TXN_PER_MICROBLOCK ] = { 0 };
188 0 : int executed_results [ MAX_TXN_PER_MICROBLOCK ] = { 0 };
189 0 : uint consumed_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
190 :
191 0 : void * pre_balance_info = fd_ext_bank_pre_balance_info( ctx->_bank, ctx->txn_abi_mem, sanitized_txn_cnt );
192 :
193 0 : void * load_and_execute_output = fd_ext_bank_load_and_execute_txns( ctx->_bank,
194 0 : ctx->txn_abi_mem,
195 0 : sanitized_txn_cnt,
196 0 : load_results,
197 0 : executing_results,
198 0 : executed_results,
199 0 : consumed_cus );
200 :
201 0 : ulong sanitized_idx = 0UL;
202 0 : for( ulong i=0; i<txn_cnt; i++ ) {
203 0 : fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );
204 :
205 0 : uint requested_cus = txn->pack_cu.requested_execution_cus;
206 0 : uint non_execution_cus = txn->pack_cu.non_execution_cus;
207 : /* Assume failure, set below if success. If it doesn't land in the
208 : block, rebate the non-execution CUs too. */
209 0 : txn->bank_cu.rebated_cus = requested_cus + non_execution_cus;
210 0 : txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
211 0 : if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue;
212 :
213 0 : sanitized_idx++;
214 0 : ctx->metrics.txn_load[ load_results[ sanitized_idx-1 ] ]++;
215 0 : if( FD_UNLIKELY( load_results[ sanitized_idx-1 ] ) ) continue;
216 :
217 0 : ctx->metrics.txn_executing[ executing_results[ sanitized_idx-1 ] ]++;
218 0 : if( FD_UNLIKELY( executing_results[ sanitized_idx-1 ] ) ) continue;
219 :
220 0 : ctx->metrics.txn_executed[ executed_results[ sanitized_idx-1 ] ]++;
221 0 : txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
222 0 : uint executed_cus = consumed_cus[ sanitized_idx-1UL ];
223 0 : txn->bank_cu.actual_consumed_cus = non_execution_cus + executed_cus;
224 0 : if( FD_UNLIKELY( executed_cus>requested_cus ) ) {
225 : /* There's basically a bug in the Agave codebase right now
226 : regarding the cost model for some transactions. Some built-in
227 : instructions like creating an address lookup table consume more
228 : CUs than the cost model allocates for them, which is only
229 : allowed because the runtime computes requested CUs differently
230 : from the cost model. Rather than implement a broken system,
231 : we'll just permit the risk of slightly overpacking blocks by
232 : ignoring these transactions when it comes to rebating. */
233 0 : FD_LOG_INFO(( "Transaction executed %u CUs but only requested %u CUs", executed_cus, requested_cus ));
234 0 : FD_MCNT_INC( BANK_TILE, COST_MODEL_UNDERCOUNT, 1UL );
235 0 : txn->bank_cu.rebated_cus = 0U;
236 0 : continue;
237 0 : }
238 0 : txn->bank_cu.rebated_cus = requested_cus - executed_cus;
239 0 : }
240 :
241 : /* Commit must succeed so no failure path. This function takes
242 : ownership of the load_and_execute_output and pre_balance_info heap
243 : allocations and will free them before it returns. They should not
244 : be reused. Once commit is called, the transactions MUST be mixed
245 : into the PoH otherwise we will fork and diverge, so the link from
246 : here til PoH mixin must be completely reliable with nothing dropped. */
247 0 : fd_ext_bank_commit_txns( ctx->_bank, ctx->txn_abi_mem, sanitized_txn_cnt, load_and_execute_output, pre_balance_info );
248 0 : pre_balance_info = NULL;
249 0 : load_and_execute_output = NULL;
250 :
251 : /* Indicate to pack tile we are done processing the transactions so
252 : it can pack new microblocks using these accounts. This has to be
253 : done after commiting the transactions to poh otherwise there is a
254 : race. */
255 0 : fd_fseq_update( ctx->busy_fseq, seq );
256 :
257 : /* Now produce the merkle hash of the transactions for inclusion
258 : (mixin) to the PoH hash. This is done on the bank tile because
259 : it shards / scales horizontally here, while PoH does not. */
260 0 : fd_microblock_trailer_t * trailer = (fd_microblock_trailer_t *)( dst + txn_cnt*sizeof(fd_txn_p_t) );
261 0 : hash_transactions( ctx->bmtree, (fd_txn_p_t*)dst, txn_cnt, trailer->hash );
262 :
263 : /* MAX_MICROBLOCK_SZ - (MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t)) == 64
264 : so there's always 64 extra bytes at the end to stash the hash. */
265 0 : FD_STATIC_ASSERT( MAX_MICROBLOCK_SZ-(MAX_TXN_PER_MICROBLOCK*sizeof(fd_txn_p_t))>=sizeof(fd_microblock_trailer_t), poh_shred_mtu );
266 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 );
267 :
268 : /* We have a race window with the GUI, where if the slot is ending it
269 : will snap these metrics to draw the waterfall, but see them outdated
270 : because housekeeping hasn't run. For now just update them here, but
271 : PoH should eventually flush the pipeline before ending the slot. */
272 0 : metrics_write( ctx );
273 :
274 0 : ulong bank_sig = fd_disco_bank_sig( slot, ctx->_microblock_idx );
275 :
276 : /* We always need to publish, even if there are no successfully executed
277 : transactions so the PoH tile can keep an accurate count of microblocks
278 : it has seen. */
279 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
280 0 : ulong new_sz = txn_cnt*sizeof(fd_txn_p_t) + sizeof(fd_microblock_trailer_t);
281 0 : fd_stem_publish( stem, 0UL, bank_sig, ctx->out_chunk, new_sz, 0UL, 0UL, tspub );
282 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, new_sz, ctx->out_chunk0, ctx->out_wmark );
283 0 : }
284 :
285 : static void
286 : unprivileged_init( fd_topo_t * topo,
287 0 : fd_topo_tile_t * tile ) {
288 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
289 :
290 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
291 0 : fd_bank_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_bank_ctx_t ), sizeof( fd_bank_ctx_t ) );
292 0 : void * blake3 = FD_SCRATCH_ALLOC_APPEND( l, FD_BLAKE3_ALIGN, FD_BLAKE3_FOOTPRINT );
293 0 : void * bmtree = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT(0) );
294 0 : ctx->txn_abi_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BANK_ABI_TXN_ALIGN, MAX_TXN_PER_MICROBLOCK*FD_BANK_ABI_TXN_FOOTPRINT );
295 0 : ctx->txn_sidecar_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BANK_ABI_TXN_ALIGN, FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR_MAX );
296 :
297 0 : #define NONNULL( x ) (__extension__({ \
298 0 : __typeof__((x)) __x = (x); \
299 0 : if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
300 0 : __x; }))
301 :
302 0 : ctx->kind_id = tile->kind_id;
303 0 : ctx->blake3 = NONNULL( fd_blake3_join( fd_blake3_new( blake3 ) ) );
304 0 : ctx->bmtree = NONNULL( bmtree );
305 :
306 0 : ulong busy_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "bank_busy.%lu", tile->kind_id );
307 0 : FD_TEST( busy_obj_id!=ULONG_MAX );
308 0 : ctx->busy_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, busy_obj_id ) );
309 0 : if( FD_UNLIKELY( !ctx->busy_fseq ) ) FD_LOG_ERR(( "banking tile %lu has no busy flag", tile->kind_id ));
310 :
311 0 : memset( &ctx->metrics, 0, sizeof( ctx->metrics ) );
312 :
313 0 : ctx->pack_in_mem = topo->workspaces[ topo->objs[ topo->links[ tile->in_link_id[ 0UL ] ].dcache_obj_id ].wksp_id ].wksp;
314 0 : ctx->pack_in_chunk0 = fd_dcache_compact_chunk0( ctx->pack_in_mem, topo->links[ tile->in_link_id[ 0UL ] ].dcache );
315 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 );
316 :
317 0 : ctx->out_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
318 0 : ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
319 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 );
320 0 : ctx->out_chunk = ctx->out_chunk0;
321 0 : }
322 :
323 0 : #define STEM_BURST (1UL)
324 :
325 : /* See explanation in fd_pack */
326 0 : #define STEM_LAZY (128L*3000L)
327 :
328 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_bank_ctx_t
329 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_bank_ctx_t)
330 :
331 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
332 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
333 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
334 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
335 :
336 : #include "../../../../disco/stem/fd_stem.c"
337 :
338 : fd_topo_run_tile_t fd_tile_bank = {
339 : .name = "bank",
340 : .scratch_align = scratch_align,
341 : .scratch_footprint = scratch_footprint,
342 : .unprivileged_init = unprivileged_init,
343 : .run = stem_run,
344 : };
|