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