Line data Source code
1 : #include "../../disco/topo/fd_topo.h"
2 : #include "../../discof/fd_startup.h"
3 : #include "../../util/log/fd_log.h"
4 : #include "../../tango/dcache/fd_dcache.h"
5 : #include "../../tango/fd_tango_base.h"
6 :
7 : #include <errno.h>
8 : #include <fcntl.h>
9 : #include <stdio.h>
10 : #include <time.h>
11 : #include <unistd.h>
12 : #include <sys/stat.h>
13 :
14 : #include "../../flamenco/capture/fd_capture_ctx.h"
15 : #include "../../flamenco/capture/fd_solcap_writer.h"
16 : #include "generated/fd_solcap_tile_seccomp.h"
17 :
18 :
19 : /* The solcap tile is responsible for managing capture context
20 : for debugging runtime execution.
21 :
22 : The tile is enabled when capture context is enabled in the config.
23 :
24 : ```
25 : [capture]
26 : solcap_capture = "/path/to/filename.solcap.pcapng"
27 : ```
28 :
29 : When enabled, each tile that writes to solcap will initialize
30 : a mcache/dcache pair to use as a shared buffer to communicate with
31 : the solcap tile. Each tile that requires solcap writes will declare
32 : their own capture context to pass into runtime execution or post
33 : execution to write to the buffer via API's provided in the capture
34 : context and notify the solcap tile. The solcap tile will then
35 : process the messages from the link and write out to the file.
36 :
37 : More information about capture context in fd_capture_ctx.h
38 :
39 : Solcap Tile:
40 :
41 : The solcap tile is initialized with the incoming links from the
42 : topology, for each tile that requires solcap writes. The handling for
43 : messages is slightly altered from that of the normal stem run loop.
44 :
45 : The messages sent to the solcap tile are bounded by the size of the
46 : 10mb (size of account data) + (much smaller) header information. In
47 : order to handle the larger messages in an efficient manner,
48 : the tile providing the data will send the data in chunks of at most
49 : 128kb, on a link ~4mb. This is in order to avoid cache trashing. This
50 : is done by using a custom input selection control to shuffle the
51 : incoming frags and origin in-link only if the current message has
52 : been read completely using the SOM/EOM flags.
53 :
54 : The recent-only mode is a mode that allows for the capture of the
55 : last N slots of the execution. This is useful for debugging runtime
56 : on a live network. The mode is enabled by setting the recent_only
57 : configuration to 1. The number of slots per file is set by the
58 : recent_slots_per_file configuration. The default is 128 slots per
59 : file. The files are named recent_0.solcap, recent_1.solcap,. The
60 : files are rotated when the current file reaches the number of slots
61 : per file.
62 : */
63 :
64 : struct fd_solcap_tile_ctx {
65 : ulong tile_idx;
66 :
67 : ulong msg_idx;
68 : ushort msg_set_sig;
69 : ulong msg_set_slot;
70 : uint block_len;
71 :
72 : /* Capture context management */
73 : fd_capture_ctx_t * capture_ctx;
74 : fd_capture_link_t * capctx_type;
75 :
76 : int fd; /* Current file descriptor */
77 :
78 : /* Recent-only rotating capture state */
79 : int recent_only; /* 1 if using 2-file flip-flop, 0 for single file */
80 : int recent_fds[2]; /* File descriptors for flip-flop and system calls */
81 : ulong recent_current_idx; /* Current file index (0 or 1) */
82 : ulong recent_file_start_slot; /* Slot number when current file was started (ULONG_MAX = uninitialized) */
83 : ulong recent_slots_per_file; /* Number of slots per file */
84 :
85 : /* Incoming links for mcache/dcache processing */
86 : struct {
87 : fd_wksp_t * mem;
88 : ulong chunk0;
89 : ulong wmark;
90 : ulong mtu;
91 : } in[32];
92 :
93 : ulong in_cnt;
94 :
95 : /* Track which input link we're currently processing */
96 : ulong current_in_idx; /* ULONG_MAX means no active message */
97 :
98 : };
99 :
100 : typedef struct fd_solcap_tile_ctx fd_solcap_tile_ctx_t;
101 :
102 : FD_FN_CONST static inline ulong
103 0 : scratch_align( void ) {
104 0 : return 128UL;
105 0 : }
106 :
107 : FD_FN_PURE static inline ulong
108 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
109 0 : (void)tile;
110 0 : ulong l = FD_LAYOUT_INIT;
111 0 : l = FD_LAYOUT_APPEND ( l, alignof(fd_solcap_tile_ctx_t), sizeof(fd_solcap_tile_ctx_t) );
112 0 : l = FD_LAYOUT_APPEND ( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
113 0 : return FD_LAYOUT_FINI( l, scratch_align() );
114 0 : }
115 :
116 : static ulong
117 : populate_allowed_seccomp( fd_topo_t const * topo,
118 : fd_topo_tile_t const * tile,
119 : ulong out_cnt,
120 0 : struct sock_filter * out ) {
121 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
122 0 : fd_solcap_tile_ctx_t const * ctx = (fd_solcap_tile_ctx_t const *)scratch;
123 :
124 0 : uint solcap_fd_0 = ctx->recent_only ? (uint)ctx->recent_fds[0] : (uint)ctx->fd;
125 0 : uint solcap_fd_1 = ctx->recent_only ? (uint)ctx->recent_fds[1] : (uint)ctx->fd;
126 :
127 0 : populate_sock_filter_policy_fd_solcap_tile( out_cnt,
128 0 : out,
129 0 : (uint)fd_log_private_logfile_fd(),
130 0 : solcap_fd_0,
131 0 : solcap_fd_1 );
132 0 : return sock_filter_policy_fd_solcap_tile_instr_cnt;
133 0 : }
134 :
135 : static ulong
136 : populate_allowed_fds( fd_topo_t const * topo,
137 : fd_topo_tile_t const * tile,
138 : ulong out_fds_cnt FD_PARAM_UNUSED,
139 0 : int * out_fds ) {
140 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
141 0 : fd_solcap_tile_ctx_t const * ctx = (fd_solcap_tile_ctx_t const *)scratch;
142 :
143 0 : ulong out_cnt = 0UL;
144 :
145 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
146 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
147 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd();
148 :
149 0 : if( ctx->recent_only ) {
150 : /* In recent_only mode, allow both flip-flop file descriptors */
151 0 : if( FD_LIKELY( -1!=ctx->recent_fds[0] ) )
152 0 : out_fds[ out_cnt++ ] = ctx->recent_fds[0];
153 0 : if( FD_LIKELY( -1!=ctx->recent_fds[1] ) )
154 0 : out_fds[ out_cnt++ ] = ctx->recent_fds[1];
155 0 : } else {
156 : /* Traditional single file mode */
157 0 : if( FD_LIKELY( -1!=ctx->fd ) )
158 0 : out_fds[ out_cnt++ ] = ctx->fd;
159 0 : }
160 :
161 0 : return out_cnt;
162 0 : }
163 :
164 : /* fd_capctx_buf_process_msg processes a message fragment from the
165 : shared buffer and writes it to the solcap file using the solcap
166 : writer API.
167 :
168 : Returns block_len, the total PCAPNG Enhanced Packet Block (EPB)
169 : length in bytes. This value represents the complete size of the
170 : PCAPNG block including:
171 : - EPB header (28 bytes)
172 : - Internal chunk header
173 : - Message payload data
174 : - Padding (to align to 4-byte boundary)
175 : - Block footer (4 bytes)
176 :
177 : For messages that span multiple fragments:
178 : - Only the first fragment (SOM) returns a non-zero block_len,
179 : representing the total calculated block size
180 : - Continuation fragments return 0 as they don't write EPB headers
181 : - The block_len from SOM is saved and used when writing the EPB
182 : footer on the final fragment (EOM) */
183 : /* fd_capctx_buf_process_som processes the first fragment (SOM) of a
184 : message and writes the appropriate header structures.
185 :
186 : Returns block_len, the total PCAPNG Enhanced Packet Block (EPB)
187 : length in bytes for this message. */
188 : uint
189 : fd_capctx_buf_process_som( fd_solcap_tile_ctx_t * ctx,
190 : fd_solcap_buf_msg_t * msg_hdr,
191 0 : char * actual_data ) {
192 0 : uint block_len = 0U;
193 0 : FD_TEST( ctx->capture_ctx->capture != NULL );
194 :
195 0 : switch( msg_hdr->sig ) {
196 0 : case SOLCAP_WRITE_ACCOUNT: {
197 0 : fd_solcap_account_update_hdr_t * account_update = fd_type_pun( actual_data );
198 0 : block_len = fd_solcap_write_account_hdr( ctx->capture_ctx->capture, msg_hdr, account_update );
199 0 : break;
200 0 : }
201 0 : case SOLCAP_WRITE_BANK_PREIMAGE: {
202 0 : fd_solcap_bank_preimage_t * bank_preimage = fd_type_pun( actual_data );
203 0 : block_len = fd_solcap_write_bank_preimage( ctx->capture_ctx->capture, msg_hdr, bank_preimage );
204 0 : break;
205 0 : }
206 0 : case SOLCAP_STAKE_REWARDS_BEGIN: {
207 0 : fd_solcap_stake_rewards_begin_t * stake_rewards_begin = fd_type_pun( actual_data );
208 0 : block_len = fd_solcap_write_stake_rewards_begin( ctx->capture_ctx->capture, msg_hdr, stake_rewards_begin );
209 0 : break;
210 0 : }
211 0 : case SOLCAP_STAKE_REWARD_EVENT: {
212 0 : fd_solcap_stake_reward_event_t * stake_reward_event = fd_type_pun( actual_data );
213 0 : block_len = fd_solcap_write_stake_reward_event( ctx->capture_ctx->capture, msg_hdr, stake_reward_event );
214 0 : break;
215 0 : }
216 0 : case SOLCAP_STAKE_ACCOUNT_PAYOUT: {
217 0 : fd_solcap_stake_account_payout_t * stake_account_payout = fd_type_pun( actual_data );
218 0 : block_len = fd_solcap_write_stake_account_payout( ctx->capture_ctx->capture, msg_hdr, stake_account_payout );
219 0 : break;
220 0 : }
221 0 : default:
222 : /* Unknown signal received in message processing */
223 0 : FD_LOG_ERR(( "Unknown signal received in message processing: sig=%lu", (ulong)msg_hdr->sig ));
224 0 : break;
225 0 : }
226 0 : return block_len;
227 0 : }
228 :
229 : /* fd_capctx_buf_process_continuation processes continuation fragments
230 : (SOM=0) which contain raw data bytes to append to the current message.
231 : This is generic and works for any fragmented message type (account
232 : updates, or any future large message types). */
233 : void
234 : fd_capctx_buf_process_continuation( fd_solcap_tile_ctx_t * ctx,
235 : char * data,
236 0 : ulong data_sz ) {
237 0 : FD_TEST( ctx->capture_ctx->capture != NULL );
238 0 : fd_solcap_write_data( ctx->capture_ctx->capture, data, data_sz );
239 0 : }
240 :
241 : /* returnable_frag processes incoming message fragments and handles
242 : fragmented solcap messages using SOM (Start of Message) and EOM (End
243 : of Message) control flags.
244 :
245 : Message Fragmentation:
246 : ----------------------
247 : Solcap messages can be very large (up to 10MB for account data). To
248 : avoid cache thrashing and fit within the ~4MB link size, large
249 : messages are split into smaller fragments of at most 128KB
250 : (SOLCAP_WRITE_ACCOUNT_DATA_MTU).
251 :
252 : SOM/EOM Control Flags:
253 : ----------------------
254 : Each fragment has two control flags set in the frag metadata:
255 : - SOM (Start of Message): Set on the first fragment of a message
256 : - EOM (End of Message): Set on the last fragment of a message
257 :
258 : For a single-fragment message:
259 : Single Fragment: SOM=1, EOM=1
260 : For a multi-fragment message:
261 : First fragment: SOM=1, EOM=0
262 : Middle fragments: SOM=0, EOM=0
263 : Last fragment: SOM=0, EOM=1
264 :
265 : Fragment Processing:
266 : --------------------
267 : When SOM is set:
268 : - The fragment begins with a fd_solcap_buf_msg_t header containing
269 : the message type (sig), slot, and transaction index
270 : - This header is parsed and saved in the context (msg_set_sig,
271 : msg_set_slot)
272 : - The input link is locked (current_in_idx) to ensure all fragments
273 : of this message are processed sequentially from the same link
274 : - The actual data follows the header in the fragment
275 : - The PCAPNG block_len is calculated and saved (ctx->block_len) for
276 : use when writing the footer on EOM
277 :
278 : When SOM is not set (continuation fragment):
279 : - The entire fragment is data (no header)
280 : - The previously saved message state is used
281 : - No block_len is calculated (continuation data is appended)
282 :
283 : When EOM is set:
284 : - The PCAPNG block footer is written using the block_len from SOM
285 : - For bank preimage messages, the file is synced to disk
286 : - All message state is reset (msg_idx, block_len, msg_set_sig, etc.)
287 : - The input link is unlocked (current_in_idx = ULONG_MAX) to allow
288 : processing the next message
289 :
290 : This design allows the solcap tile to handle arbitrarily large
291 : messages while maintaining efficient memory usage and cache locality.
292 : */
293 :
294 : static inline int
295 : returnable_frag( fd_solcap_tile_ctx_t * ctx,
296 : ulong in_idx,
297 : ulong seq FD_PARAM_UNUSED,
298 : ulong sig FD_PARAM_UNUSED,
299 : ulong chunk,
300 : ulong sz,
301 : ulong ctl,
302 : ulong tsorig FD_PARAM_UNUSED,
303 : ulong tspub FD_PARAM_UNUSED,
304 0 : fd_stem_context_t * stem FD_PARAM_UNUSED ) {
305 :
306 0 : if( FD_UNLIKELY( sz!=0UL && (chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
307 0 : FD_LOG_ERR(( "chunk %lu %lu from in %lu corrupt, not in range [%lu,%lu]", chunk, sz, in_idx, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
308 :
309 :
310 : /* If we're processing a message from a different input, skip this
311 : fragment for now without incrementing input seq number */
312 0 : if( FD_UNLIKELY( ctx->current_in_idx != ULONG_MAX && ctx->current_in_idx != in_idx ) ) return 1;
313 :
314 0 : uchar const * data = fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk );
315 :
316 0 : int som = fd_frag_meta_ctl_som( ctl );
317 0 : int eom = fd_frag_meta_ctl_eom( ctl );
318 :
319 0 : if( som ) {
320 0 : fd_solcap_buf_msg_t * msg_hdr = fd_type_pun( (void *)data );
321 0 : char * actual_data = (char *)(data + sizeof(fd_solcap_buf_msg_t));
322 0 : FD_TEST( sz >= sizeof(fd_solcap_buf_msg_t) );
323 0 : ctx->msg_set_slot = msg_hdr->slot;
324 0 : ctx->msg_set_sig = (ushort)msg_hdr->sig;
325 0 : ctx->current_in_idx = in_idx; /* Start tracking this input */
326 :
327 : /* Handle file rotation for recent_only mode */
328 0 : if( ctx->recent_only ) {
329 0 : if( ctx->recent_file_start_slot == ULONG_MAX ) {
330 0 : ctx->recent_file_start_slot = msg_hdr->slot;
331 0 : }
332 0 : else if( msg_hdr->slot >= ctx->recent_file_start_slot + ctx->recent_slots_per_file ) {
333 : /* Check if we need to rotate (>= slots_per_file slots from start) */
334 : /* Flip-flop to the other file */
335 0 : ulong next_idx = 1UL - ctx->recent_current_idx;
336 0 : int next_fd = ctx->recent_fds[next_idx];
337 :
338 : /* The following is a series of checks to ensure the file is
339 : synced and truncated correctly. This occurs via:
340 : 1. Syncing the current file
341 : 2. Syncing the next file
342 : 3. Truncating the next file
343 : 4. Resetting the file descriptor position to 0
344 : 5. Reinitializing the solcap writer with the new file descriptor
345 : */
346 0 : FD_TEST( fsync( ctx->fd ) == 0 );
347 0 : FD_TEST( ftruncate( next_fd, 0L ) == 0 );
348 0 : FD_TEST( lseek( next_fd, 0L, SEEK_SET ) == 0L );
349 :
350 0 : fd_solcap_writer_init( ctx->capture_ctx->capture, next_fd );
351 0 : ctx->recent_current_idx = next_idx;
352 0 : ctx->recent_file_start_slot = msg_hdr->slot;
353 0 : ctx->fd = next_fd;
354 0 : }
355 0 : }
356 :
357 0 : uint block_len = fd_capctx_buf_process_som( ctx, msg_hdr, actual_data );
358 0 : FD_TEST( block_len > 0 ); /* SOM must return valid block length */
359 0 : ctx->block_len = block_len;
360 0 : } else {
361 : /* Continuation fragment: just raw data bytes */
362 0 : fd_capctx_buf_process_continuation( ctx, (char *)data, sz );
363 0 : }
364 :
365 : /* If message you receive has the eom flag, write footer */
366 0 : if( eom ) {
367 0 : FD_TEST( ctx->block_len > 0 ); /* Must have valid block length before writing footer */
368 0 : fd_solcap_write_ftr( ctx->capture_ctx->capture, ctx->block_len );
369 :
370 0 : ctx->msg_idx = 0;
371 0 : ctx->block_len = 0;
372 0 : ctx->msg_set_sig = 0;
373 0 : ctx->msg_set_slot = 0;
374 0 : ctx->current_in_idx = ULONG_MAX; /* Reset to sentinel - ready for next message */
375 0 : } else {
376 0 : ctx->msg_idx++;
377 0 : }
378 :
379 0 : return 0;
380 0 : }
381 :
382 : static void
383 : privileged_init( fd_topo_t const * topo,
384 0 : fd_topo_tile_t const * tile ) {
385 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
386 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
387 0 : fd_solcap_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_solcap_tile_ctx_t), sizeof(fd_solcap_tile_ctx_t) );
388 0 : void * _capture_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
389 :
390 0 : ctx->tile_idx = tile->kind_id;
391 :
392 0 : ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) );
393 0 : FD_TEST( ctx->capture_ctx );
394 :
395 0 : ctx->recent_only = tile->solcap.recent_only;
396 0 : ctx->recent_slots_per_file = tile->solcap.recent_slots_per_file ? tile->solcap.recent_slots_per_file : 128UL;
397 :
398 0 : struct stat path_stat;
399 0 : int stat_result = stat( tile->solcap.solcap_capture, &path_stat );
400 :
401 0 : if( ctx->recent_only ) {
402 : /* recent_only=1: Ensure path is a directory, create if not exists */
403 0 : if( stat_result != 0 ) {
404 0 : if( FD_UNLIKELY( mkdir(tile->solcap.solcap_capture, 0755) != 0 ) ) {
405 0 : FD_LOG_ERR(( "solcap_recent_only=1 but could not create directory: %s (%i-%s)",
406 0 : tile->solcap.solcap_capture, errno, strerror(errno) ));
407 0 : }
408 0 : } else if( FD_UNLIKELY( !S_ISDIR(path_stat.st_mode) ) ) {
409 0 : FD_LOG_ERR(( "solcap_recent_only=1 but path is not a directory: %s", tile->solcap.solcap_capture ));
410 0 : }
411 :
412 0 : ctx->recent_current_idx = 0;
413 0 : ctx->recent_file_start_slot = ULONG_MAX; /* Will be set on first fragment */
414 :
415 0 : for( ulong i = 0; i < 2; i++ ) {
416 0 : char filepath[PATH_MAX];
417 0 : int ret = snprintf( filepath, PATH_MAX, "%s/recent_%lu.solcap", tile->solcap.solcap_capture, i );
418 0 : if( FD_UNLIKELY( ret<0 || ret>=PATH_MAX ) ) {
419 0 : FD_LOG_ERR(( "snprintf failed or path too long for recent file %lu", i ));
420 0 : }
421 :
422 0 : ctx->recent_fds[i] = open( filepath, O_RDWR | O_CREAT | O_TRUNC, 0644 );
423 0 : if( FD_UNLIKELY( ctx->recent_fds[i] == -1 ) ) {
424 0 : FD_LOG_ERR(( "failed to open or create solcap recent file %s (%i-%s)",
425 0 : filepath, errno, strerror(errno) ));
426 0 : }
427 0 : }
428 :
429 0 : ctx->fd = ctx->recent_fds[0];
430 :
431 0 : } else {
432 : /* recent_only=0: Validate that path is a file*/
433 0 : if( FD_UNLIKELY( stat_result == 0 && S_ISDIR(path_stat.st_mode) ) ) {
434 0 : FD_LOG_ERR(( "solcap_recent_only=0 but path is a directory: %s (should be a file path)", tile->solcap.solcap_capture ));
435 0 : }
436 :
437 0 : ctx->fd = open( tile->solcap.solcap_capture, O_RDWR | O_CREAT | O_TRUNC, 0644 );
438 0 : if( FD_UNLIKELY( ctx->fd == -1 ) ) {
439 0 : FD_LOG_ERR(( "failed to open or create solcap capture file %s (%i-%s)",
440 0 : tile->solcap.solcap_capture, errno, strerror(errno) ));
441 0 : }
442 0 : }
443 :
444 0 : FD_TEST( ctx->capture_ctx->capture );
445 :
446 0 : ctx->capture_ctx->capture_solcap = 1;
447 0 : ctx->capture_ctx->solcap_start_slot = tile->solcap.capture_start_slot;
448 0 : fd_solcap_writer_init( ctx->capture_ctx->capture, ctx->fd );
449 :
450 0 : ctx->current_in_idx = ULONG_MAX; /* No active message initially */
451 0 : ctx->msg_idx = 0UL;
452 0 : ctx->msg_set_sig = 0U;
453 0 : ctx->msg_set_slot = 0UL;
454 0 : ctx->block_len = 0U;
455 :
456 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
457 :
458 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
459 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
460 :
461 0 : }
462 :
463 : static void
464 : unprivileged_init( fd_topo_t const * topo,
465 0 : fd_topo_tile_t const * tile ) {
466 :
467 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
468 0 : fd_solcap_tile_ctx_t * ctx = (fd_solcap_tile_ctx_t *)scratch;
469 :
470 0 : ctx->in_cnt = 0UL;
471 0 : FD_TEST( tile->in_cnt <= 32UL );
472 0 : for( ulong i = 0UL; i < tile->in_cnt; i++ ) {
473 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
474 0 : fd_topo_wksp_t const * wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
475 :
476 0 : ctx->in[ctx->in_cnt].mem = wksp->wksp;
477 0 : ctx->in[ctx->in_cnt].chunk0 = fd_dcache_compact_chunk0( wksp->wksp, link->dcache );
478 0 : ctx->in[ctx->in_cnt].wmark = fd_dcache_compact_wmark( wksp->wksp, link->dcache, link->mtu );
479 0 : ctx->in[ctx->in_cnt].mtu = link->mtu;
480 0 : ctx->in_cnt++;
481 0 : }
482 :
483 0 : fd_sleep_until_replay_started( topo );
484 0 : }
485 :
486 0 : #define STEM_BURST (1UL)
487 0 : #define STEM_LAZY (50UL)
488 :
489 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_solcap_tile_ctx_t
490 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_solcap_tile_ctx_t)
491 :
492 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
493 :
494 : #include "../../disco/stem/fd_stem.c"
495 :
496 : fd_topo_run_tile_t fd_tile_solcap = {
497 : .name = "solcap",
498 : .populate_allowed_seccomp = populate_allowed_seccomp,
499 : .populate_allowed_fds = populate_allowed_fds,
500 : .scratch_align = scratch_align,
501 : .scratch_footprint = scratch_footprint,
502 : .privileged_init = privileged_init,
503 : .unprivileged_init = unprivileged_init,
504 : .run = stem_run
505 : };
|