Line data Source code
1 : #define _GNU_SOURCE
2 :
3 : #include "../../choreo/fd_choreo.h"
4 : #include "../../disco/fd_disco.h"
5 : #include "../../disco/keyguard/fd_keyload.h"
6 : #include "../../disco/topo/fd_topo.h"
7 : #include "../../disco/shred/fd_stake_ci.h"
8 : #include "../../flamenco/runtime/fd_runtime.h"
9 : #include "../../funk/fd_funk.h"
10 : #include "../../funk/fd_funk_val.h"
11 : #include "generated/fd_tower_tile_seccomp.h"
12 :
13 0 : #define IN_KIND_GOSSIP ( 0)
14 0 : #define IN_KIND_REPLAY ( 1)
15 0 : #define IN_KIND_SHRED ( 2)
16 0 : #define IN_KIND_SIGN ( 3)
17 : #define MAX_IN_LINKS (16)
18 :
19 : #define SIGN_OUT_IDX (0)
20 :
21 : #define VOTER_MAX ( 4096UL )
22 : #define VOTER_FOOTPRINT ( 40UL ) /* serialized footprint */
23 :
24 : typedef struct {
25 : fd_wksp_t * mem;
26 : ulong chunk0;
27 : ulong wmark;
28 : ulong mtu;
29 : } in_ctx_t;
30 :
31 : typedef struct {
32 : fd_pubkey_t identity_key[1];
33 : fd_pubkey_t vote_acc[1];
34 : fd_funk_rec_key_t funk_key;
35 : ulong seed;
36 :
37 : uchar in_kind [MAX_IN_LINKS];
38 : in_ctx_t in_links[MAX_IN_LINKS];
39 :
40 : ulong replay_out_idx;
41 :
42 : ulong send_out_idx;
43 : fd_wksp_t * send_out_mem;
44 : ulong send_out_chunk0;
45 : ulong send_out_wmark;
46 : ulong send_out_chunk;
47 :
48 : fd_epoch_t * epoch;
49 : fd_ghost_t * ghost;
50 : fd_tower_t * tower;
51 :
52 : ulong root;
53 : ulong processed; /* highest processed slot (replayed & counted votes) */
54 : ulong confirmed; /* highest confirmed slot (2/3 of stake has voted) */
55 : ulong finalized; /* highest finalized slot (2/3 of stake has rooted) */
56 :
57 : fd_hash_t bank_hash;
58 : fd_hash_t block_hash;
59 : fd_gossip_duplicate_shred_t duplicate_shred;
60 : uchar duplicate_shred_chunk[FD_EQVOC_PROOF_CHUNK_SZ];
61 : uchar * epoch_voters_buf;
62 : char funk_file[PATH_MAX];
63 : fd_funk_t funk[1];
64 : fd_gossip_vote_t gossip_vote;
65 : fd_lockout_offset_t lockouts[FD_TOWER_VOTE_MAX];
66 : fd_tower_t * scratch;
67 : uchar * vote_ix_buf;
68 : } ctx_t;
69 :
70 : static void
71 0 : update_epoch( ctx_t * ctx, ulong sz ) {
72 0 : fd_voter_t * epoch_voters = fd_epoch_voters( ctx->epoch );
73 0 : ctx->epoch->total_stake = 0;
74 :
75 0 : ulong off = 0;
76 0 : while( FD_LIKELY( off < sz ) ) {
77 0 : fd_pubkey_t pubkey = *(fd_pubkey_t *)fd_type_pun( ctx->epoch_voters_buf + off );
78 0 : off += sizeof(fd_pubkey_t);
79 :
80 0 : ulong stake = *(ulong *)fd_type_pun( ctx->epoch_voters_buf + off );
81 0 : off += sizeof(ulong);
82 :
83 0 : # if FD_EPOCH_USE_HANDHOLDING
84 0 : FD_TEST( !fd_epoch_voters_query( epoch_voters, pubkey, NULL ) );
85 0 : FD_TEST( fd_epoch_voters_key_cnt( epoch_voters ) < fd_epoch_voters_key_max( epoch_voters ) );
86 0 : # endif
87 :
88 0 : fd_voter_t * voter = fd_epoch_voters_insert( epoch_voters, pubkey );
89 0 : voter->rec.uc[FD_FUNK_REC_KEY_FOOTPRINT - 1] = FD_FUNK_KEY_TYPE_ACC;
90 :
91 0 : # if FD_EPOCH_USE_HANDHOLDING
92 0 : FD_TEST( 0 == memcmp( &voter->key, &pubkey, sizeof(fd_pubkey_t) ) );
93 0 : FD_TEST( fd_epoch_voters_query( epoch_voters, voter->key, NULL ) );
94 0 : # endif
95 :
96 0 : voter->stake = stake;
97 0 : voter->replay_vote = FD_SLOT_NULL;
98 0 : voter->gossip_vote = FD_SLOT_NULL;
99 0 : voter->rooted_vote = FD_SLOT_NULL;
100 :
101 0 : ctx->epoch->total_stake += stake;
102 0 : }
103 0 : }
104 :
105 : static void
106 0 : update_ghost( ctx_t * ctx, fd_funk_txn_t * txn ) {
107 0 : fd_funk_t * funk = ctx->funk;
108 0 : fd_epoch_t * epoch = ctx->epoch;
109 0 : fd_ghost_t * ghost = ctx->ghost;
110 :
111 0 : fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
112 0 : for( ulong i = 0; i < fd_epoch_voters_slot_cnt( epoch_voters ); i++ ) {
113 0 : if( FD_LIKELY( fd_epoch_voters_key_inval( epoch_voters[i].key ) ) ) continue /* most slots are empty */;
114 :
115 : /* TODO we can optimize this funk query to only check through the
116 : last slot on this fork this function was called on. currently
117 : rec_query_global traverses all the way back to the root. */
118 :
119 0 : fd_voter_t * voter = &epoch_voters[i];
120 :
121 : /* Fetch the vote account's vote slot and root slot from the vote
122 : account, re-trying if there is a Funk conflict. */
123 :
124 0 : ulong vote = FD_SLOT_NULL;
125 0 : ulong root = FD_SLOT_NULL;
126 :
127 0 : for(;;) {
128 0 : fd_funk_rec_query_t query;
129 0 : fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( funk, txn, &voter->rec, NULL, &query );
130 0 : if( FD_UNLIKELY( !rec ) ) break;
131 0 : fd_voter_state_t const * state = fd_voter_state( funk, rec );
132 0 : if( FD_UNLIKELY( !state ) ) break;
133 0 : vote = fd_voter_state_vote( state );
134 0 : root = fd_voter_state_root( state );
135 0 : if( FD_LIKELY( fd_funk_rec_query_test( &query ) == FD_FUNK_SUCCESS ) ) break;
136 0 : }
137 :
138 : /* Only process votes for slots >= root. Ghost requires vote slot
139 : to already exist in the ghost tree. */
140 :
141 0 : if( FD_LIKELY( vote != FD_SLOT_NULL && vote >= fd_ghost_root( ghost )->slot ) ) {
142 0 : fd_ghost_replay_vote( ghost, voter, vote );
143 :
144 : /* Check if it has crossed the equivocation safety and optimistic
145 : confirmation thresholds. */
146 :
147 0 : fd_ghost_node_t const * node = fd_ghost_query( ghost, vote );
148 :
149 : /* Error if the node's vote slot is not in ghost. This is an
150 : invariant violation, because we know their tower must be on the
151 : same fork as this current one that we're processing, and so by
152 : definition their vote slot must be in our ghost (ie. we can't
153 : have rooted past it or be on a different fork). */
154 :
155 0 : if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "[%s] voter %s's vote slot %lu was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), vote ));
156 :
157 0 : fd_ghost_replay_vote( ghost, voter, vote );
158 0 : double pct = (double)node->replay_stake / (double)epoch->total_stake;
159 0 : if( FD_UNLIKELY( pct > FD_CONFIRMED_PCT ) ) ctx->confirmed = fd_ulong_max( ctx->confirmed, node->slot );
160 0 : }
161 :
162 : /* Check if this voter's root >= ghost root. We can't process
163 : other voters' roots that precede the ghost root. */
164 :
165 0 : if( FD_LIKELY( root != FD_SLOT_NULL && root >= fd_ghost_root( ghost )->slot ) ) {
166 0 : fd_ghost_node_t const * node = fd_ghost_query( ghost, root );
167 :
168 : /* Error if the node's root slot is not in ghost. This is an
169 : invariant violation, because we know their tower must be on the
170 : same fork as this current one that we're processing, and so by
171 : definition their root slot must be in our ghost (ie. we can't
172 : have rooted past it or be on a different fork). */
173 :
174 0 : if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "[%s] voter %s's root slot %lu was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), root ));
175 :
176 0 : fd_ghost_rooted_vote( ghost, voter, root );
177 0 : double pct = (double)node->rooted_stake / (double)epoch->total_stake;
178 0 : if( FD_UNLIKELY( pct > FD_FINALIZED_PCT ) ) ctx->finalized = fd_ulong_max( ctx->finalized, node->slot );
179 0 : }
180 0 : }
181 0 : }
182 :
183 : FD_FN_CONST static inline ulong
184 0 : scratch_align( void ) {
185 0 : return 128UL;
186 0 : }
187 :
188 : FD_FN_PURE static inline ulong
189 0 : scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
190 0 : return FD_LAYOUT_FINI(
191 0 : FD_LAYOUT_APPEND(
192 0 : FD_LAYOUT_APPEND(
193 0 : FD_LAYOUT_APPEND(
194 0 : FD_LAYOUT_APPEND(
195 0 : FD_LAYOUT_APPEND(
196 0 : FD_LAYOUT_APPEND(
197 0 : FD_LAYOUT_INIT,
198 0 : alignof(ctx_t), sizeof(ctx_t) ),
199 0 : fd_epoch_align(), fd_epoch_footprint( FD_VOTER_MAX ) ),
200 0 : fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ) ),
201 0 : fd_tower_align(), fd_tower_footprint() ), /* our tower */
202 0 : fd_tower_align(), fd_tower_footprint() ), /* scratch */
203 0 : 128UL, VOTER_FOOTPRINT * VOTER_MAX ), /* scratch */
204 0 : scratch_align() );
205 0 : }
206 :
207 : static void
208 : during_frag( ctx_t * ctx,
209 : ulong in_idx,
210 : ulong seq FD_PARAM_UNUSED,
211 : ulong sig,
212 : ulong chunk,
213 : ulong sz,
214 0 : ulong ctl FD_PARAM_UNUSED ) {
215 0 : uint in_kind = ctx->in_kind[in_idx];
216 0 : in_ctx_t const * in_ctx = &ctx->in_links[in_idx];
217 0 : switch( in_kind ) {
218 :
219 0 : case IN_KIND_GOSSIP: {
220 0 : uchar const * chunk_laddr = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
221 0 : switch(sig) {
222 0 : case fd_crds_data_enum_vote: {
223 0 : memcpy( &ctx->vote_ix_buf[0], chunk_laddr, sz );
224 0 : break;
225 0 : }
226 0 : case fd_crds_data_enum_duplicate_shred: {
227 0 : memcpy( &ctx->duplicate_shred, chunk_laddr, sizeof(fd_gossip_duplicate_shred_t) );
228 0 : memcpy( ctx->duplicate_shred_chunk, chunk_laddr + sizeof(fd_gossip_duplicate_shred_t), FD_EQVOC_PROOF_CHUNK_SZ );
229 0 : break;
230 0 : }
231 0 : default: {
232 0 : FD_LOG_ERR(( "unexpected crds discriminant %lu", sig ));
233 0 : break;
234 0 : }
235 0 : }
236 0 : break;
237 0 : }
238 :
239 0 : case IN_KIND_REPLAY: {
240 0 : in_ctx_t const * in_ctx = &ctx->in_links[ in_idx ];
241 0 : ulong parent_slot = fd_ulong_extract_lsb( sig, 32 );
242 0 : uchar const * chunk_laddr = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
243 0 : if( FD_UNLIKELY( parent_slot == UINT_MAX /* no parent, so snapshot slot */ ) ) {
244 0 : fd_memcpy( ctx->epoch_voters_buf, chunk_laddr, sz );
245 0 : } else {
246 0 : memcpy( ctx->bank_hash.uc, chunk_laddr, sizeof(fd_hash_t) );
247 0 : memcpy( ctx->block_hash.uc, chunk_laddr+sizeof(fd_hash_t), sizeof(fd_hash_t) );
248 0 : }
249 0 : break;
250 0 : }
251 :
252 0 : case IN_KIND_SHRED:
253 0 : break;
254 :
255 0 : case IN_KIND_SIGN:
256 0 : break;
257 :
258 0 : default:
259 0 : FD_LOG_ERR(( "Unknown in_kind %u", in_kind ));
260 0 : }
261 0 : }
262 :
263 : static void
264 : after_frag( ctx_t * ctx,
265 : ulong in_idx,
266 : ulong seq FD_PARAM_UNUSED,
267 : ulong sig,
268 : ulong sz,
269 : ulong tsorig FD_PARAM_UNUSED,
270 : ulong tspub FD_PARAM_UNUSED,
271 0 : fd_stem_context_t * stem FD_PARAM_UNUSED ) {
272 0 : uint in_kind = ctx->in_kind[in_idx];
273 0 : if( FD_UNLIKELY( in_kind != IN_KIND_REPLAY ) ) return;
274 :
275 0 : ulong slot = fd_ulong_extract( sig, 32, 63 );
276 0 : ulong parent_slot = fd_ulong_extract_lsb( sig, 32 );
277 :
278 0 : if( FD_UNLIKELY( (uint)parent_slot == UINT_MAX ) ) { /* snapshot slot */
279 0 : FD_TEST( ctx->funk );
280 0 : FD_TEST( fd_funk_txn_map( ctx->funk ) );
281 0 : update_epoch( ctx, sz );
282 0 : fd_ghost_init( ctx->ghost, slot );
283 0 : return;
284 0 : }
285 :
286 0 : fd_funk_txn_xid_t txn_xid = { .ul = { slot, slot } };
287 0 : fd_funk_txn_map_t * txn_map = fd_funk_txn_map( ctx->funk );
288 0 : fd_funk_txn_start_read( ctx->funk );
289 0 : fd_funk_txn_t * funk_txn = fd_funk_txn_query( &txn_xid, txn_map );
290 0 : if( FD_UNLIKELY( !funk_txn ) ) FD_LOG_ERR(( "Could not find valid funk transaction" ));
291 0 : fd_funk_txn_end_read( ctx->funk );
292 :
293 : /* Initialize the tower */
294 :
295 0 : if( FD_UNLIKELY( fd_tower_votes_empty( ctx->tower ) ) ) fd_tower_from_vote_acc( ctx->tower, ctx->funk, funk_txn, &ctx->funk_key );
296 :
297 0 : fd_ghost_node_t const * ghost_node = fd_ghost_insert( ctx->ghost, parent_slot, slot );
298 0 : FD_TEST( ghost_node );
299 0 : update_ghost( ctx, funk_txn );
300 :
301 0 : ulong vote_slot = fd_tower_vote_slot( ctx->tower, ctx->epoch, ctx->funk, funk_txn, ctx->ghost, ctx->scratch );
302 0 : if( FD_UNLIKELY( vote_slot == FD_SLOT_NULL ) ) return; /* nothing to vote on */
303 :
304 0 : ulong root = fd_tower_vote( ctx->tower, vote_slot );
305 0 : if( FD_LIKELY( root != FD_SLOT_NULL ) ) {
306 0 : fd_ghost_publish( ctx->ghost, root );
307 0 : fd_stem_publish( stem, ctx->replay_out_idx, root, 0UL, 0UL, 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
308 0 : ctx->root = root;
309 0 : }
310 :
311 : /* Send our updated tower to the cluster. */
312 :
313 0 : fd_txn_p_t * vote_txn = (fd_txn_p_t *)fd_chunk_to_laddr( ctx->send_out_mem, ctx->send_out_chunk );
314 0 : fd_tower_to_vote_txn( ctx->tower, ctx->root, ctx->lockouts, &ctx->bank_hash, &ctx->block_hash, ctx->identity_key, ctx->identity_key, ctx->vote_acc, vote_txn );
315 0 : FD_TEST( !fd_tower_votes_empty( ctx->tower ) );
316 0 : FD_TEST( vote_txn->payload_sz > 0UL );
317 0 : fd_stem_publish( stem, ctx->send_out_idx, vote_slot, ctx->send_out_chunk, sizeof(fd_txn_p_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
318 :
319 0 : fd_ghost_print( ctx->ghost, ctx->epoch, fd_ghost_root( ctx->ghost ) );
320 0 : fd_tower_print( ctx->tower, ctx->root );
321 0 : }
322 :
323 : static void
324 : unprivileged_init( fd_topo_t * topo,
325 0 : fd_topo_tile_t * tile ) {
326 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
327 :
328 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
329 0 : ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
330 0 : void * epoch_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_epoch_align(), fd_epoch_footprint( FD_VOTER_MAX ) );
331 0 : void * ghost_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ) );
332 0 : void * tower_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
333 0 : void * scratch_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
334 0 : void * voter_mem = FD_SCRATCH_ALLOC_APPEND( l, 128UL, VOTER_FOOTPRINT * VOTER_MAX );
335 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI ( l, scratch_align() );
336 0 : FD_TEST( scratch_top == (ulong)scratch + scratch_footprint( tile ) );
337 :
338 0 : ctx->epoch = fd_epoch_join( fd_epoch_new( epoch_mem, FD_VOTER_MAX ) );
339 0 : ctx->ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 42UL, FD_BLOCK_MAX ) );
340 0 : ctx->tower = fd_tower_join( fd_tower_new( tower_mem ) );
341 0 : ctx->scratch = fd_tower_join( fd_tower_new( scratch_mem ) );
342 :
343 0 : if( FD_UNLIKELY( !fd_funk_join( ctx->funk, fd_topo_obj_laddr( topo, tile->tower.funk_obj_id ) ) ) ) {
344 0 : FD_LOG_ERR(( "Failed to join database cache" ));
345 0 : }
346 :
347 0 : ctx->epoch_voters_buf = voter_mem;
348 :
349 0 : memcpy( ctx->identity_key->uc, fd_keyload_load( tile->tower.identity_key_path, 1 ), sizeof(fd_pubkey_t) );
350 0 : memcpy( ctx->vote_acc->uc, fd_keyload_load( tile->tower.vote_acc_path, 1 ), sizeof(fd_pubkey_t) );
351 :
352 0 : memset( ctx->funk_key.uc, 0, sizeof(fd_funk_rec_key_t) );
353 0 : memcpy( ctx->funk_key.uc, ctx->vote_acc->uc, sizeof(fd_pubkey_t) );
354 0 : ctx->funk_key.uc[FD_FUNK_REC_KEY_FOOTPRINT - 1] = FD_FUNK_KEY_TYPE_ACC;
355 :
356 0 : if( FD_UNLIKELY( tile->in_cnt > MAX_IN_LINKS ) ) FD_LOG_ERR(( "repair tile has too many input links" ));
357 :
358 0 : for( uint in_idx=0U; in_idx<(tile->in_cnt); in_idx++ ) {
359 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ in_idx ] ];
360 0 : if( 0==strcmp( link->name, "gossip_tower" ) ) {
361 0 : ctx->in_kind[ in_idx ] = IN_KIND_GOSSIP;
362 0 : } else if( 0==strcmp( link->name, "replay_tower" ) ) {
363 0 : ctx->in_kind[ in_idx ] = IN_KIND_REPLAY;
364 0 : } else if( 0==strcmp( link->name, "stake_out" ) ) {
365 0 : ctx->in_kind[ in_idx ] = IN_KIND_SHRED;
366 0 : } else {
367 0 : FD_LOG_ERR(( "tower tile has unexpected input link %s", link->name ));
368 0 : }
369 0 : ctx->in_links[ in_idx ].mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
370 0 : ctx->in_links[ in_idx ].chunk0 = fd_dcache_compact_chunk0( ctx->in_links[ in_idx ].mem, link->dcache );
371 0 : ctx->in_links[ in_idx ].wmark = fd_dcache_compact_wmark ( ctx->in_links[ in_idx ].mem, link->dcache, link->mtu );
372 0 : ctx->in_links[ in_idx ].mtu = link->mtu;
373 0 : }
374 :
375 0 : ctx->replay_out_idx = fd_topo_find_tile_out_link( topo, tile, "tower_replay", 0 );
376 0 : FD_TEST( ctx->replay_out_idx!= ULONG_MAX );
377 :
378 0 : ctx->send_out_idx = fd_topo_find_tile_out_link( topo, tile, "tower_send", 0 );
379 0 : FD_TEST( ctx->send_out_idx!=ULONG_MAX );
380 0 : fd_topo_link_t * send_out = &topo->links[ tile->out_link_id[ ctx->send_out_idx ] ];
381 0 : ctx->send_out_mem = topo->workspaces[ topo->objs[ send_out->dcache_obj_id ].wksp_id ].wksp;
382 0 : ctx->send_out_chunk0 = fd_dcache_compact_chunk0( ctx->send_out_mem, send_out->dcache );
383 0 : ctx->send_out_wmark = fd_dcache_compact_wmark ( ctx->send_out_mem, send_out->dcache, send_out->mtu );
384 0 : ctx->send_out_chunk = ctx->send_out_chunk0;
385 0 : FD_TEST( fd_dcache_compact_is_safe( ctx->send_out_mem, send_out->dcache, send_out->mtu, send_out->depth ) );
386 0 : }
387 :
388 : static ulong
389 : populate_allowed_seccomp( fd_topo_t const * topo,
390 : fd_topo_tile_t const * tile,
391 : ulong out_cnt,
392 0 : struct sock_filter * out ) {
393 0 : (void)topo;
394 0 : (void)tile;
395 :
396 0 : populate_sock_filter_policy_fd_tower_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
397 0 : return sock_filter_policy_fd_tower_tile_instr_cnt;
398 0 : }
399 :
400 : static ulong
401 : populate_allowed_fds( fd_topo_t const * topo,
402 : fd_topo_tile_t const * tile,
403 : ulong out_fds_cnt,
404 0 : int * out_fds ) {
405 0 : (void)topo;
406 0 : (void)tile;
407 :
408 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
409 :
410 0 : ulong out_cnt = 0UL;
411 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
412 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
413 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
414 0 : return out_cnt;
415 0 : }
416 :
417 0 : #define STEM_BURST (1UL)
418 :
419 0 : #define STEM_CALLBACK_CONTEXT_TYPE ctx_t
420 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(ctx_t)
421 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
422 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
423 :
424 : #include "../../disco/stem/fd_stem.c"
425 :
426 : fd_topo_run_tile_t fd_tile_tower = {
427 : .name = "tower",
428 : .populate_allowed_seccomp = populate_allowed_seccomp,
429 : .populate_allowed_fds = populate_allowed_fds,
430 : .scratch_align = scratch_align,
431 : .scratch_footprint = scratch_footprint,
432 : .unprivileged_init = unprivileged_init,
433 : .run = stem_run,
434 : };
|