Line data Source code
1 : #include "../../disco/topo/fd_topo.h"
2 : #include "../../disco/events/generated/fd_event_gen.h"
3 : #include "../../disco/keyguard/fd_keyswitch.h"
4 : #include "../../disco/keyguard/fd_keyload.h"
5 :
6 : #include "fd_adminctl.h"
7 : #include "generated/fd_admin_tile_seccomp.h"
8 :
9 : struct fd_admin_tile_ctx {
10 : fd_topo_t const * topo;
11 : fd_adminctl_t * adminctl;
12 : uchar identity_pubkey[ 32UL ];
13 : fd_keyswitch_t * tower_av_keyswitch;
14 : fd_keyswitch_t * txsend_av_keyswitch;
15 : fd_keyswitch_t * sign_av_keyswitch[ FD_TOPO_MAX_TILES ];
16 : ulong sign_av_keyswitch_cnt;
17 : fd_sha512_t sha512[ 1 ];
18 :
19 : ulong replay_out_idx; /* admin_replay stem out index */
20 : ulong snap_create_slot_idx; /* adminctl slot of snapshot-create command */
21 : ulong snap_create_target_slot; /* requested slot retained until Replay responds */
22 : ulong snap_create_start_time; /* command start retained until Replay responds */
23 : };
24 :
25 : typedef struct fd_admin_tile_ctx fd_admin_tile_ctx_t;
26 :
27 : static inline fd_event_admin_command_t
28 : prepare_admin_command( int type,
29 : void const * payload,
30 0 : ulong payload_sz ) {
31 0 : fd_event_admin_command_t event = {
32 0 : .type = type,
33 0 : .args_json = { '{', '}' },
34 0 : .args_json_len = 2UL,
35 0 : .start_time = (ulong)fd_log_wallclock(),
36 0 : .payload_size = payload_sz,
37 0 : .has_payload_version = 0,
38 0 : };
39 0 : if( FD_LIKELY( payload_sz>=sizeof(ulong) ) ) {
40 0 : event.payload_version = FD_LOAD( ulong, payload );
41 0 : event.has_payload_version = 1;
42 0 : }
43 0 : return event;
44 0 : }
45 :
46 : static inline void
47 : report_admin_command( fd_event_admin_command_t * event,
48 0 : int result ) {
49 0 : FD_TEST( result>=0 && result<=FD_EVENT_ADMIN_COMMAND_RESULT_CUSTOM );
50 0 : event->result = result;
51 0 : event->end_time = (ulong)fd_log_wallclock();
52 0 : fd_event_report_admin_command( event );
53 0 : }
54 :
55 : static inline void
56 : report_admin_command_custom_result( fd_event_admin_command_t * event,
57 0 : char const * custom_result ) {
58 0 : FD_TEST( fd_cstr_printf_check( (char *)event->custom_result,
59 0 : sizeof(event->custom_result),
60 0 : &event->custom_result_len,
61 0 : "%s",
62 0 : custom_result ) );
63 0 : report_admin_command( event, FD_EVENT_ADMIN_COMMAND_RESULT_CUSTOM );
64 0 : }
65 :
66 : FD_FN_CONST static inline ulong
67 0 : scratch_align( void ) {
68 0 : return alignof(fd_admin_tile_ctx_t);
69 0 : }
70 :
71 : FD_FN_PURE static inline ulong
72 0 : scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
73 0 : return sizeof(fd_admin_tile_ctx_t);
74 0 : }
75 :
76 : static void
77 : privileged_init( fd_topo_t const * topo,
78 0 : fd_topo_tile_t const * tile ) {
79 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
80 0 : fd_admin_tile_ctx_t * ctx = (fd_admin_tile_ctx_t *)scratch;
81 0 : fd_memset( ctx, 0, sizeof(fd_admin_tile_ctx_t) );
82 :
83 0 : if( FD_UNLIKELY( !strcmp( tile->admin.identity_key_path, "" ) ) )
84 0 : FD_LOG_ERR(( "identity_key_path not set" ));
85 :
86 0 : fd_memcpy( ctx->identity_pubkey, fd_keyload_load( tile->admin.identity_key_path, /* pubkey only: */ 1 ), 32UL );
87 0 : }
88 :
89 : static void
90 : unprivileged_init( fd_topo_t const * topo,
91 0 : fd_topo_tile_t const * tile ) {
92 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
93 0 : fd_admin_tile_ctx_t * ctx = (fd_admin_tile_ctx_t *)scratch;
94 0 : ctx->replay_out_idx = ULONG_MAX;
95 0 : ctx->snap_create_slot_idx = ULONG_MAX;
96 0 : ctx->topo = topo;
97 :
98 0 : fd_topo_obj_t const * adminctl_obj = fd_topo_find_tile_obj( topo, tile, "adminctl" );
99 0 : FD_TEST( adminctl_obj );
100 :
101 0 : ctx->adminctl = fd_adminctl_join( fd_topo_obj_laddr( topo, adminctl_obj->id ) );
102 0 : FD_TEST( ctx->adminctl );
103 :
104 0 : ctx->replay_out_idx = fd_topo_find_tile_out_link( topo, tile, "admin_replay", 0UL );
105 :
106 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
107 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
108 0 : if( FD_UNLIKELY( strcmp( link->name, "replay_admin" ) ) ) {
109 0 : FD_LOG_ERR(( "unexpected input link name %s", link->name ));
110 0 : }
111 0 : }
112 :
113 0 : ulong tower_idx = fd_topo_find_tile( topo, "tower", 0UL );
114 0 : if( FD_LIKELY( tower_idx!=ULONG_MAX ) ) {
115 0 : FD_TEST( topo->tiles[ tower_idx ].av_keyswitch_obj_id!=ULONG_MAX );
116 0 : ctx->tower_av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, topo->tiles[ tower_idx ].av_keyswitch_obj_id ) );
117 0 : FD_TEST( ctx->tower_av_keyswitch );
118 0 : } else {
119 0 : ctx->tower_av_keyswitch = NULL;
120 0 : }
121 :
122 0 : ulong txsend_idx = fd_topo_find_tile( topo, "txsend", 0UL );
123 0 : FD_TEST( txsend_idx!=ULONG_MAX );
124 0 : FD_TEST( topo->tiles[ txsend_idx ].av_keyswitch_obj_id!=ULONG_MAX );
125 0 : ctx->txsend_av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, topo->tiles[ txsend_idx ].av_keyswitch_obj_id ) );
126 0 : FD_TEST( ctx->txsend_av_keyswitch );
127 :
128 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
129 0 : fd_topo_tile_t const * sign_tile = &topo->tiles[ i ];
130 0 : if( FD_LIKELY( strcmp( sign_tile->name, "sign" ) ) ) continue;
131 0 : FD_TEST( sign_tile->av_keyswitch_obj_id!=ULONG_MAX );
132 0 : ctx->sign_av_keyswitch[ ctx->sign_av_keyswitch_cnt ] = fd_keyswitch_join( fd_topo_obj_laddr( topo, sign_tile->av_keyswitch_obj_id ) );
133 0 : FD_TEST( ctx->sign_av_keyswitch[ ctx->sign_av_keyswitch_cnt ] );
134 0 : ctx->sign_av_keyswitch_cnt++;
135 0 : }
136 0 : FD_TEST( ctx->sign_av_keyswitch_cnt );
137 :
138 0 : FD_TEST( fd_sha512_join( fd_sha512_new( ctx->sha512 ) ) );
139 0 : }
140 :
141 : /* The process of switching identity of the validator is somewhat
142 : involved, to prevent it from producing torn data (for example,
143 : a block where half the shreds are signed by one private key, and half
144 : are signed by another).
145 :
146 : The process of switching is a state machine that progresses linearly
147 : through each of the states. Generally, no transitions are allowed
148 : except direct forward steps, except in emergency recovery cases an
149 : operator can force the state past the initial lock.
150 :
151 : The states follow, in order. */
152 :
153 : /* State 0: UNLOCKED.
154 : The validator is not currently in the process of switching keys. */
155 0 : #define FD_SET_IDENTITY_STATE_UNLOCKED (0UL)
156 :
157 : /* State 1: LOCKED
158 : Some client to the validator has requested a key switch. To do so,
159 : it acquired an exclusive lock on the validator to prevent the
160 : switch potentially being interleaved with another client. */
161 0 : #define FD_SET_IDENTITY_STATE_LOCKED (1UL)
162 :
163 : /* State 2: LEADER_HALT_REQUESTED
164 : The first step in the key switch process is to pause the leader
165 : pipeline of the validator, preventing us from becoming leader, but
166 : finishing any currently in progress leader slot if there is one.
167 : While in this state, the validator is waiting for the leader
168 : pipeline to confirm that it has paused production, and is no longer
169 : leader.
170 :
171 : In Firedancer, this halt request goes to the Replay tile, which
172 : causes the tile to switch the identity key it uses to determine the
173 : identity's balance as well as when the validator is the leader.
174 : After the leader pipeline has been halted, the validator will no
175 : longer become a leader until the switch has been completed. */
176 0 : #define FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED (2UL)
177 :
178 : /* State 3: LEADER_HALTED
179 : The Replay tile has confirmed that it has halted the leader
180 : pipeline, and the validator is no longer leader. No more blocks
181 : will be produced until it is unhalted. In addition, the Replay
182 : tile has switched its own identity key.
183 :
184 : At this point, we also have the guarantee that there are no more
185 : outstanding shreds that have to be signed with the old key. Any
186 : tiles related to the leader pipeline that rely on the identity key
187 : will not be used. */
188 0 : #define FD_SET_IDENTITY_STATE_LEADER_HALTED (3UL)
189 :
190 : /* State 4: SIGNERS_HALT_REQUESTED
191 : Repair, Gossip, Tower, and Bundle tiles will stop sending requests
192 : downstream to the sign tile. This is done to avoid any mismatches
193 : with the identity key. Their identity keys will be switched during
194 : this step, except for Gossip, which switches during
195 : SIGNERS_UNHALT_REQUESTED. These tiles all use the identity key to
196 : make forward progress on non-leader pipeline replay except for the
197 : Bundle tile.
198 :
199 : These tiles use the identity key to populate messages which are
200 : signed by the sign tile:
201 : (a) Repair. The repair tile uses the identity key as part of the
202 : repair protocol. The identity key is included in and used
203 : for signing requests. Because Repair uses an asynchronous
204 : signing mechanism, Repair will first wait until all
205 : outstanding sign requests have been received back from the
206 : sign tile before halting any new signing requests.
207 : (b) Gossip. The gossip tile sends out ContactInfo messages with
208 : our identity key, and also uses the identity key to sign
209 : outgoing gossip messages.
210 : (c) Tower. The tower tile uses the identity key to generate
211 : vote transactions which are sent to the send tile. These
212 : vote transactions are then signed downstream by the TxSend
213 : tile instead of having its own keyguard client.
214 : (d) Bundle. The bundle tile uses the identity key to sign an
215 : authentication challenge from the bundle server.
216 : (e) Rserve. The rserve tile uses the identity key to sign
217 : outgoing pings.
218 : */
219 0 : #define FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED (4UL)
220 :
221 : /* State 5: SIGNERS_HALTED
222 : Repair, Gossip, Tower, and Bundle are no longer sending requests to
223 : the sign tile. Replay can keep progressing at this point.
224 : However, the Tower tile may have an in-flight vote transaction to
225 : the TxSend tile that corresponds to the old identity key. */
226 0 : #define FD_SET_IDENTITY_STATE_SIGNERS_HALTED (5UL)
227 :
228 : /* State 6: TXSEND_FLUSH_REQUESTED
229 : Once the Tower tile has updated its identity key and stopped
230 : sending vote transactions to the TxSend tile, any in-flight vote
231 : transactions for the old identity key must be flushed to avoid
232 : being badly signed. We also know that Tower will send no more
233 : vote transactions to the TxSend tile.
234 :
235 : The TxSend tile is flushed by telling it the last sequence number
236 : the Tower tile has produced for an outgoing vote transaction at the
237 : time it was halted. Once the TxSend tile has processed all vote
238 : transactions up to and including that sequence number, it will
239 : switch its own identity key. There is a guarantee that the TxSend
240 : tile will not request to sign any vote transactions until it is
241 : unhalted. At this point, the TxSend tile will stop receiving any
242 : new frags from the Net tile. The reason for this is to avoid any
243 : QUIC callbacks that invoke key signing. */
244 0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED (6UL)
245 :
246 : /* State 7: TXSEND_FLUSHED
247 : The TxSend tile confirms that it has seen and processed all votes
248 : up to and including the last sequence number produced by the Tower
249 : tile at the time it was halted. The TxSend tile also switches its
250 : own identity key which is used for signing votes and establishing
251 : a QUIC connection. The TxSend tile is now no longer receiving any
252 : new frags from the Net tile. */
253 0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSHED (7UL)
254 :
255 : /* State 8: ALL_SWITCH_REQUESTED
256 : The client now requests that all other tiles which consume the
257 : identity key in some way switch to the new key. The leader
258 : pipeline is still halted, although it doesn't strictly need to be,
259 : since outgoing shreds have been flushed. This is done to keep the
260 : control flow simpler. The sign tile's switch is requested first to
261 : avoid any potential mismatches with the identity key.
262 :
263 : The other tiles using the identity key are:
264 : (a) Sign. The sign tile is responsible for holding the private
265 : key and servicing signing requests from other tiles.
266 : (b) GUI. The GUI shows the validator identity key to the user,
267 : and uses the key to determine which blocks are ours for
268 : highlighting on the frontend.
269 : (c) Gossvf. The gossvf tile uses the identity key to detect
270 : duplicate running instances of the same validator node as
271 : well as other message handling.
272 : (d) Shred. The shred tile uses the identity key to determine the
273 : position of the validator in the Turbine tree and to sign
274 : outgoing shreds.
275 : (e) Event. Outgoing events to the event server are signed with
276 : the identity key to authenticate the sender. */
277 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED (8UL)
278 :
279 : /* State 9: ALL_SWITCHED
280 : All remaining tiles that use the identity key have confirmed that
281 : they have switched to the new key. Gossip has not yet updated its
282 : identity key. Repair, Gossip, Tower, TxSend, and Bundle remain
283 : halted. */
284 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCHED (9UL)
285 :
286 : /* State 10: SIGNERS_UNHALT_REQUESTED
287 : During this state, the tiles that rely on the sign tile can be
288 : safely unhalted and have their keys switched. After this state,
289 : all tiles will be using the switched identity key. */
290 0 : #define FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED (10UL)
291 :
292 : /* State 11: SIGNERS_UNHALTED
293 : All tiles that rely on the sign tile have been unhalted, and the
294 : validator can now resume making progress on replay. */
295 0 : #define FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED (11UL)
296 :
297 : /* State 12: LEADER_UNHALT_REQUESTED
298 : The final state, now that all tiles have switched, the leader
299 : pipeline can be unblocked and the validator can resume producing
300 : blocks. The next state once the Replay tile confirms the leader
301 : pipeline is unlocked, is UNLOCKED. */
302 0 : #define FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED (12UL)
303 :
304 : static fd_keyswitch_t *
305 : find_identity_keyswitch( fd_admin_tile_ctx_t * ctx,
306 0 : char const * tile_name ) {
307 0 : fd_topo_t const * topo = ctx->topo;
308 0 : ulong tile_idx = fd_topo_find_tile( topo, tile_name, 0UL );
309 0 : FD_TEST( tile_idx!=ULONG_MAX );
310 0 : FD_TEST( topo->tiles[ tile_idx ].id_keyswitch_obj_id!=ULONG_MAX );
311 :
312 0 : fd_keyswitch_t * keyswitch = fd_topo_obj_laddr( topo, topo->tiles[ tile_idx ].id_keyswitch_obj_id );
313 0 : FD_TEST( keyswitch );
314 0 : return keyswitch;
315 0 : }
316 :
317 : static int FD_FN_SENSITIVE
318 : poll_set_identity( fd_admin_tile_ctx_t * ctx,
319 : ulong * state,
320 : ulong * halted_seq,
321 : ulong identity_outset,
322 0 : uchar * keypair ) {
323 0 : fd_topo_t const * topo = ctx->topo;
324 :
325 0 : switch( *state ) {
326 0 : case FD_SET_IDENTITY_STATE_UNLOCKED: {
327 0 : fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" );
328 0 : if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &replay->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
329 0 : *state = FD_SET_IDENTITY_STATE_LOCKED;
330 0 : FD_LOG_INFO(( "Locking validator identity for key switch..." ));
331 0 : } else {
332 0 : FD_LOG_CRIT(( "identity keyswitch is in a locked state but should be unlocked" ));
333 0 : }
334 0 : break;
335 0 : }
336 0 : case FD_SET_IDENTITY_STATE_LOCKED: {
337 0 : fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" );
338 0 : memcpy( replay->bytes, keypair+32UL, 32UL );
339 :
340 0 : FD_COMPILER_MFENCE();
341 0 : replay->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
342 0 : FD_COMPILER_MFENCE();
343 0 : *state = FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED;
344 0 : FD_LOG_INFO(( "Pausing leader pipeline for key switch..." ));
345 0 : break;
346 0 : }
347 0 : case FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED: {
348 0 : fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" );
349 0 : if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
350 0 : fd_memzero_explicit( replay->bytes, 64UL );
351 0 : FD_COMPILER_MFENCE();
352 0 : *halted_seq = replay->result;
353 0 : *state = FD_SET_IDENTITY_STATE_LEADER_HALTED;
354 0 : FD_LOG_INFO(( "Leader pipeline successfully paused..." ));
355 0 : } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
356 0 : FD_SPIN_PAUSE();
357 0 : } else {
358 0 : FD_LOG_ERR(( "Unexpected replay keyswitch state %lu", replay->state ));
359 0 : }
360 0 : break;
361 0 : }
362 0 : case FD_SET_IDENTITY_STATE_LEADER_HALTED: {
363 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
364 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
365 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
366 0 : if( strcmp( tile->name, "repair" ) &&
367 0 : strcmp( tile->name, "gossip" ) &&
368 0 : strcmp( tile->name, "tower" ) &&
369 0 : strcmp( tile->name, "bundle" ) &&
370 0 : strcmp( tile->name, "rserve" ) ) {
371 0 : continue;
372 0 : }
373 :
374 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
375 0 : if( !strcmp( tile->name, "gossip" ) ) tile_ks->param = identity_outset;
376 0 : memcpy( tile_ks->bytes, keypair+32UL, 32UL );
377 0 : FD_COMPILER_MFENCE();
378 0 : tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
379 0 : FD_COMPILER_MFENCE();
380 0 : }
381 0 : *state = FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED;
382 0 : FD_LOG_INFO(( "Requesting to halt all signers..." ));
383 0 : break;
384 0 : }
385 0 : case FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED: {
386 0 : int all_switched = 1;
387 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
388 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
389 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
390 0 : if( strcmp( tile->name, "repair" ) &&
391 0 : strcmp( tile->name, "gossip" ) &&
392 0 : strcmp( tile->name, "tower" ) &&
393 0 : strcmp( tile->name, "bundle" ) &&
394 0 : strcmp( tile->name, "rserve" ) ) {
395 0 : continue;
396 0 : }
397 :
398 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
399 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
400 0 : all_switched = 0;
401 0 : break;
402 0 : }
403 0 : }
404 0 : if( FD_LIKELY( all_switched ) ) {
405 0 : FD_LOG_INFO(( "All signers successfully halted..." ));
406 0 : *state = FD_SET_IDENTITY_STATE_SIGNERS_HALTED;
407 0 : } else {
408 0 : FD_SPIN_PAUSE();
409 0 : }
410 0 : break;
411 0 : }
412 0 : case FD_SET_IDENTITY_STATE_SIGNERS_HALTED: {
413 0 : ulong tower_halted_seq = find_identity_keyswitch( ctx, "tower" )->result;
414 0 : fd_keyswitch_t * txsend = find_identity_keyswitch( ctx, "txsend" );
415 0 : txsend->param = tower_halted_seq;
416 0 : memcpy( txsend->bytes, keypair+32UL, 32UL );
417 0 : FD_COMPILER_MFENCE();
418 0 : txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
419 0 : FD_COMPILER_MFENCE();
420 :
421 0 : *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED;
422 0 : break;
423 0 : }
424 0 : case FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED: {
425 0 : fd_keyswitch_t * txsend = find_identity_keyswitch( ctx, "txsend" );
426 0 : if( FD_LIKELY( txsend->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
427 0 : fd_memzero_explicit( txsend->bytes, 64UL );
428 0 : FD_COMPILER_MFENCE();
429 0 : *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSHED;
430 0 : } else {
431 0 : FD_SPIN_PAUSE();
432 0 : }
433 0 : break;
434 0 : }
435 0 : case FD_SET_IDENTITY_STATE_TXSEND_FLUSHED: {
436 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
437 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
438 0 : if( strcmp( tile->name, "sign" ) ) continue;
439 0 : fd_keyswitch_t * sign = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
440 0 : memcpy( sign->bytes, keypair, 64UL );
441 0 : FD_COMPILER_MFENCE();
442 0 : sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
443 0 : FD_COMPILER_MFENCE();
444 0 : }
445 :
446 0 : fd_memzero_explicit( keypair, 32UL ); /* Private key no longer needed by the admin tile. */
447 :
448 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
449 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
450 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
451 0 : if( FD_LIKELY( !strcmp( tile->name, "sign" ) ||
452 0 : !strcmp( tile->name, "replay" ) ||
453 0 : !strcmp( tile->name, "repair" ) ||
454 0 : !strcmp( tile->name, "gossip" ) ||
455 0 : !strcmp( tile->name, "txsend" ) ||
456 0 : !strcmp( tile->name, "tower" ) ||
457 0 : !strcmp( tile->name, "bundle" ) ||
458 0 : !strcmp( tile->name, "rserve" ) ) ) continue;
459 :
460 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
461 0 : if( !strcmp( tile->name, "gossvf" ) ) tile_ks->param = identity_outset;
462 0 : memcpy( tile_ks->bytes, keypair+32UL, 32UL );
463 0 : FD_COMPILER_MFENCE();
464 0 : tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
465 0 : FD_COMPILER_MFENCE();
466 0 : }
467 :
468 0 : FD_LOG_INFO(( "Requesting all remaining tiles switch identity key..." ));
469 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED;
470 0 : break;
471 0 : }
472 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED: {
473 0 : ulong all_switched = 1UL;
474 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
475 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
476 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
477 0 : if( FD_LIKELY( !strcmp( tile->name, "replay" ) ||
478 0 : !strcmp( tile->name, "repair" ) ||
479 0 : !strcmp( tile->name, "gossip" ) ||
480 0 : !strcmp( tile->name, "txsend" ) ||
481 0 : !strcmp( tile->name, "tower" ) ||
482 0 : !strcmp( tile->name, "bundle" ) ||
483 0 : !strcmp( tile->name, "rserve" ) ) ) continue;
484 :
485 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
486 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
487 0 : all_switched = 0UL;
488 0 : break;
489 0 : } else if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
490 0 : if( FD_LIKELY( !strcmp( tile->name, "sign" ) ) ) {
491 0 : FD_COMPILER_MFENCE();
492 0 : fd_memzero_explicit( tile_ks->bytes, 64UL );
493 0 : FD_COMPILER_MFENCE();
494 0 : }
495 0 : continue;
496 0 : } else {
497 0 : FD_LOG_ERR(( "Unexpected %s keyswitch state %lu", tile->name, tile_ks->state ));
498 0 : }
499 0 : }
500 :
501 0 : if( FD_LIKELY( all_switched ) ) {
502 0 : FD_LOG_INFO(( "All tiles successfully switched identity key..." ));
503 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCHED;
504 0 : } else {
505 0 : FD_SPIN_PAUSE();
506 0 : }
507 0 : break;
508 0 : }
509 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCHED: {
510 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
511 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
512 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
513 0 : if( strcmp( tile->name, "repair" ) &&
514 0 : strcmp( tile->name, "gossip" ) &&
515 0 : strcmp( tile->name, "tower" ) &&
516 0 : strcmp( tile->name, "txsend" ) &&
517 0 : strcmp( tile->name, "bundle" ) &&
518 0 : strcmp( tile->name, "rserve" ) ) {
519 0 : continue;
520 0 : }
521 :
522 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
523 0 : FD_COMPILER_MFENCE();
524 0 : tile_ks->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
525 0 : FD_COMPILER_MFENCE();
526 0 : }
527 :
528 0 : FD_LOG_INFO(( "Requesting to unpause signers..." ));
529 0 : *state = FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED;
530 0 : break;
531 0 : }
532 0 : case FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED: {
533 0 : int all_switched = 1;
534 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
535 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
536 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
537 0 : if( strcmp( tile->name, "repair" ) &&
538 0 : strcmp( tile->name, "gossip" ) &&
539 0 : strcmp( tile->name, "tower" ) &&
540 0 : strcmp( tile->name, "txsend" ) &&
541 0 : strcmp( tile->name, "bundle" ) &&
542 0 : strcmp( tile->name, "rserve" ) ) {
543 0 : continue;
544 0 : }
545 :
546 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
547 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
548 0 : all_switched = 0;
549 0 : break;
550 0 : }
551 0 : }
552 0 : if( FD_LIKELY( all_switched ) ) {
553 0 : FD_LOG_INFO(( "Successfully unpaused all non-leader signers..." ));
554 0 : *state = FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED;
555 0 : } else {
556 0 : FD_SPIN_PAUSE();
557 0 : }
558 0 : break;
559 0 : }
560 0 : case FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED: {
561 0 : fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" );
562 0 : replay->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
563 0 : FD_LOG_INFO(( "Requesting to unpause leader pipeline..." ));
564 0 : *state = FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED;
565 0 : break;
566 0 : }
567 0 : case FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED: {
568 0 : fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" );
569 0 : if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
570 0 : FD_LOG_INFO(( "Leader pipeline unpaused..." ));
571 0 : replay->state = FD_KEYSWITCH_STATE_UNLOCKED;
572 0 : *state = FD_SET_IDENTITY_STATE_UNLOCKED;
573 0 : } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
574 0 : FD_SPIN_PAUSE();
575 0 : } else {
576 0 : FD_LOG_ERR(( "Unexpected replay keyswitch state %lu", replay->state ));
577 0 : }
578 0 : break;
579 0 : }
580 0 : default:
581 0 : FD_LOG_ERR(( "Unexpected set-identity state %lu", *state ));
582 0 : }
583 :
584 0 : return *state==FD_SET_IDENTITY_STATE_UNLOCKED;
585 0 : }
586 :
587 : static void FD_FN_SENSITIVE
588 : set_identity( fd_admin_tile_ctx_t * ctx,
589 : ulong slot_idx,
590 : void * data,
591 0 : ulong data_sz ) {
592 :
593 0 : fd_adminctl_t * adminctl = ctx->adminctl;
594 0 : fd_event_admin_command_t event = prepare_admin_command( FD_EVENT_ADMIN_COMMAND_TYPE_SET_IDENTITY, data, data_sz );
595 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, old_identity );
596 0 : FD_TEST( fd_cstr_printf_check( (char *)event.args_json, sizeof(event.args_json), &event.args_json_len, "{\"old_identity\":\"%s\"}", old_identity ) );
597 :
598 0 : if( FD_UNLIKELY( data_sz<sizeof(ulong) ) ) {
599 0 : FD_LOG_WARNING(( "adminctl set-identity payload too small: %lu", data_sz ));
600 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
601 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
602 0 : return;
603 0 : }
604 :
605 0 : ulong version = FD_LOAD( ulong, data );
606 0 : if( FD_UNLIKELY( version!=FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION ) ) {
607 0 : FD_LOG_WARNING(( "unsupported adminctl set-identity payload version %lu", version ));
608 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_VERSION_MISMATCH );
609 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH );
610 0 : return;
611 0 : }
612 :
613 0 : if( FD_UNLIKELY( data_sz!=sizeof(fd_adminctl_set_identity_t) ) ) {
614 0 : FD_LOG_WARNING(( "unexpected adminctl set-identity payload_sz %lu", data_sz ));
615 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
616 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
617 0 : return;
618 0 : }
619 :
620 0 : fd_adminctl_set_identity_t * req = fd_type_pun( data );
621 :
622 0 : uchar public_key[ 32UL ];
623 0 : fd_ed25519_public_from_private( public_key, req->keypair, ctx->sha512 );
624 0 : if( FD_UNLIKELY( memcmp( public_key, req->keypair+32UL, 32UL ) ) ) {
625 0 : FD_LOG_WARNING(( "set-identity failed: public key in key file does not match private key" ));
626 0 : report_admin_command_custom_result( &event, "keypair_mismatch" );
627 0 : fd_adminctl_complete( adminctl, slot_idx, FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH );
628 0 : return;
629 0 : }
630 :
631 0 : ulong state = FD_SET_IDENTITY_STATE_UNLOCKED;
632 0 : ulong halted_seq = 0UL;
633 0 : ulong identity_outset = (ulong)fd_log_wallclock();
634 0 : for(;;) {
635 0 : if( FD_UNLIKELY( poll_set_identity( ctx, &state, &halted_seq, identity_outset, req->keypair ) ) ) break;
636 0 : }
637 :
638 0 : memcpy( ctx->identity_pubkey, req->keypair+32UL, 32UL );
639 :
640 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_SUCCESS );
641 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_SUCCESS );
642 0 : }
643 :
644 : static void
645 : get_identity( fd_admin_tile_ctx_t * ctx,
646 : ulong slot_idx,
647 : void * data,
648 0 : ulong data_sz ) {
649 :
650 0 : fd_adminctl_t * adminctl = ctx->adminctl;
651 0 : fd_event_admin_command_t event = prepare_admin_command( FD_EVENT_ADMIN_COMMAND_TYPE_GET_IDENTITY, data, data_sz );
652 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_pubkey, identity );
653 0 : FD_TEST( fd_cstr_printf_check( (char *)event.args_json, sizeof(event.args_json), &event.args_json_len, "{\"identity\":\"%s\"}", identity ) );
654 :
655 0 : if( FD_UNLIKELY( data_sz<sizeof(ulong) ) ) {
656 0 : FD_LOG_WARNING(( "adminctl get-identity payload too small: %lu", data_sz ));
657 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
658 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
659 0 : return;
660 0 : }
661 :
662 0 : ulong version = FD_LOAD( ulong, data );
663 0 : if( FD_UNLIKELY( version!=FD_ADMINCTL_GET_IDENTITY_PAYLOAD_VERSION ) ) {
664 0 : FD_LOG_WARNING(( "unsupported adminctl get-identity payload version %lu", version ));
665 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_VERSION_MISMATCH );
666 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH );
667 0 : return;
668 0 : }
669 :
670 0 : if( FD_UNLIKELY( data_sz!=sizeof(fd_adminctl_get_identity_req_t) ) ) {
671 0 : FD_LOG_WARNING(( "unexpected adminctl get-identity payload_sz %lu", data_sz ));
672 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
673 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
674 0 : return;
675 0 : }
676 :
677 : /* Adminctl commands are serviced one at a time by this tile, which is
678 : the only driver of identity switches, so the tracked identity
679 : cannot be mid-switch here. */
680 0 : fd_adminctl_get_identity_resp_t resp;
681 0 : resp.version = FD_ADMINCTL_GET_IDENTITY_PAYLOAD_VERSION;
682 0 : memcpy( resp.identity_pubkey, ctx->identity_pubkey, 32UL );
683 :
684 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_SUCCESS );
685 0 : fd_adminctl_complete_response( adminctl, slot_idx, FD_ADMINCTL_RESULT_SUCCESS, &resp, sizeof(resp) );
686 0 : }
687 :
688 : /* The process of adding an authorized voter to the validator must be
689 : done carefully in order to prevent vote transactions being generated
690 : with an authorized voter that the sign tile is not yet aware of.
691 : The authorized voter must be added to the sign tile before it is
692 : added to the tower tile. All transitions must be linear and in
693 : forward order. */
694 :
695 : /* State 0: UNLOCKED
696 : The validator is not currently in the process of switching keys. */
697 0 : #define FD_ADD_AUTH_VOTER_STATE_UNLOCKED (0UL)
698 :
699 : /* State 1: LOCKED
700 : Some client to the validator has requested to add an authorized
701 : voter. To do so, it acquired an exclusive lock on the validator to
702 : prevent the switch potentially being interleaved with another
703 : client. */
704 0 : #define FD_ADD_AUTH_VOTER_STATE_LOCKED (1UL)
705 :
706 : /* State 2: SIGN_TILE_REQUESTED
707 : The first step to add an authorized voter is to notify the sign
708 : tile that an authorized voter is being added. */
709 0 : #define FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED (2UL)
710 :
711 : /* State 3: SIGN_TILE_UPDATED
712 : The Sign tile has confirmed that it has updated its internal
713 : mapping for the set of supported authorized voters. At this point
714 : the sign tile is aware of the new authorized voter but the Tower
715 : tile will not prepare vote transactions with the new authorized
716 : voter yet. */
717 0 : #define FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED (3UL)
718 :
719 : /* State 4: TOWER_TILE_REQUESTED
720 : Once the Sign tile is updated, now the Tower tile must be notified
721 : that an authorized voter is being added so it can start preparing
722 : vote transactions with the new authorized voter. */
723 0 : #define FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED (4UL)
724 :
725 : /* State 5: TOWER_TILE_UPDATED
726 : The Tower tile has confirmed that it has updated its internal
727 : mapping for the set of supported authorized voters. */
728 0 : #define FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED (5UL)
729 :
730 : /* State 6: UNLOCK_REQUESTED
731 : The client now requests that the Tower tile unpause the pipeline
732 : so the validator can start producing votes with the new authorized
733 : voter. */
734 0 : #define FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED (6UL)
735 :
736 : static void FD_FN_SENSITIVE
737 : poll_add_authorized_voter( fd_admin_tile_ctx_t * ctx,
738 : ulong * state,
739 : uchar * keypair,
740 0 : ulong * result ) {
741 0 : fd_keyswitch_t * tower = ctx->tower_av_keyswitch;
742 :
743 0 : switch( *state ) {
744 0 : case FD_ADD_AUTH_VOTER_STATE_UNLOCKED: {
745 0 : if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &tower->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
746 0 : *state = FD_ADD_AUTH_VOTER_STATE_LOCKED;
747 0 : FD_LOG_INFO(( "Locking authorized voter set for authorized voter update..." ));
748 0 : } else {
749 : /* keyswitch changes should be guarded and ordered by adminctl.
750 : If the keyswitch is in a locked state means there is
751 : unexpected process state and the validator should crash. */
752 0 : FD_LOG_CRIT(( "keyswitch is in a locked state but should be unlocked" ));
753 0 : }
754 0 : break;
755 0 : }
756 0 : case FD_ADD_AUTH_VOTER_STATE_LOCKED: {
757 0 : for( ulong i=0UL; i<ctx->sign_av_keyswitch_cnt; i++ ) {
758 0 : fd_keyswitch_t * sign = ctx->sign_av_keyswitch[ i ];
759 0 : memcpy( sign->bytes, keypair, 64UL );
760 0 : sign->param = FD_KEYSWITCH_PARAM_AV_ADD;
761 0 : FD_COMPILER_MFENCE();
762 0 : sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
763 0 : FD_COMPILER_MFENCE();
764 0 : }
765 0 : fd_memzero_explicit( keypair, 32UL );
766 0 : *state = FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED;
767 0 : FD_LOG_INFO(( "Requesting all sign tiles to update authorized voter key set..." ));
768 0 : break;
769 0 : }
770 0 : case FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED: {
771 0 : int all_updated = 1;
772 0 : for( ulong i=0UL; i<ctx->sign_av_keyswitch_cnt; i++ ) {
773 0 : fd_keyswitch_t * sign = ctx->sign_av_keyswitch[ i ];
774 0 : if( FD_UNLIKELY( sign->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
775 0 : all_updated = 0;
776 0 : } else if( FD_UNLIKELY( sign->state==FD_KEYSWITCH_STATE_FAILED ) ) {
777 : /* Recoverable error: the sign tile failed to update the set
778 : of authorized voters is a result of bad caller input. All
779 : the sign tiles should be in sync, which means that if one
780 : sign tile failed, we expect all of them to. */
781 0 : fd_memzero_explicit( sign->bytes, 64UL );
782 0 : if( FD_LIKELY( !*result ) ) *result = sign->result;
783 0 : } else { /* sign->state==FD_KEYSWITCH_STATE_COMPLETED */
784 0 : fd_memzero_explicit( sign->bytes, 64UL );
785 0 : }
786 0 : }
787 :
788 0 : if( FD_LIKELY( all_updated ) ) {
789 0 : if( FD_UNLIKELY( *result ) ) *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED;
790 0 : else *state = FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED;
791 0 : } else {
792 0 : FD_SPIN_PAUSE();
793 0 : }
794 0 : break;
795 0 : }
796 0 : case FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED: {
797 0 : memcpy( tower->bytes, keypair+32UL, 32UL );
798 0 : tower->param = FD_KEYSWITCH_PARAM_AV_ADD;
799 0 : FD_COMPILER_MFENCE();
800 0 : tower->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
801 0 : FD_COMPILER_MFENCE();
802 0 : *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED;
803 0 : FD_LOG_INFO(( "Requesting tower tile to update authorized voter key set..." ));
804 0 : break;
805 0 : }
806 0 : case FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED: {
807 : /* There is a guarantee that the tower tile will be in sync with
808 : the set of authorized voters in the sign tile. At this point
809 : that means that the command should succeed because invariants
810 : such as not having duplicate authorized voter keys and too many
811 : authorized voters are upheld. If this doesn't hold true, the
812 : Tower tile will detect any corruption and gracefully crash the
813 : validator. */
814 0 : if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
815 0 : *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED;
816 0 : FD_LOG_INFO(( "Tower tile key set successfully updated..." ));
817 0 : } else {
818 0 : FD_SPIN_PAUSE();
819 0 : }
820 0 : break;
821 0 : }
822 0 : case FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED: {
823 0 : tower->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
824 0 : *state = FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED;
825 0 : FD_LOG_INFO(( "Requesting an unlock of the authorized voter key set..." ));
826 0 : break;
827 0 : }
828 0 : case FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED: {
829 0 : if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_UNLOCKED ) ) {
830 0 : *state = FD_ADD_AUTH_VOTER_STATE_UNLOCKED;
831 0 : FD_LOG_INFO(( "Authorized voter key set unlocked..." ));
832 0 : } else {
833 0 : FD_SPIN_PAUSE();
834 0 : }
835 0 : break;
836 0 : }
837 0 : default: {
838 0 : FD_LOG_CRIT(( "Unexpected add-authorized-voter state %lu", *state ));
839 0 : }
840 0 : }
841 0 : }
842 :
843 : static void FD_FN_SENSITIVE
844 : add_authorized_voter( fd_admin_tile_ctx_t * ctx,
845 : ulong slot_idx,
846 : void * data,
847 0 : ulong data_sz ) {
848 :
849 0 : fd_adminctl_t * adminctl = ctx->adminctl;
850 0 : fd_event_admin_command_t event = prepare_admin_command( FD_EVENT_ADMIN_COMMAND_TYPE_ADD_AUTHORIZED_VOTER, data, data_sz );
851 :
852 0 : if( FD_UNLIKELY( data_sz<sizeof(ulong) ) ) {
853 0 : FD_LOG_WARNING(( "adminctl add-authorized-voter payload too small: %lu", data_sz ));
854 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
855 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
856 0 : return;
857 0 : }
858 :
859 0 : ulong version = FD_LOAD( ulong, data );
860 0 : if( FD_UNLIKELY( version!=FD_ADMINCTL_ADD_AUTH_VOTER_PAYLOAD_VERSION ) ) {
861 0 : FD_LOG_WARNING(( "unsupported adminctl add-authorized-voter payload version %lu", version ));
862 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_VERSION_MISMATCH );
863 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH );
864 0 : return;
865 0 : }
866 :
867 0 : if( FD_UNLIKELY( data_sz!=sizeof(fd_adminctl_add_auth_voter_t) ) ) {
868 0 : FD_LOG_WARNING(( "unexpected adminctl add-authorized-voter payload_sz %lu", data_sz ));
869 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
870 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
871 0 : return;
872 0 : }
873 :
874 0 : fd_adminctl_add_auth_voter_t * req = fd_type_pun( data );
875 0 : FD_BASE58_ENCODE_32_BYTES( req->keypair+32UL, authorized_voter );
876 0 : FD_TEST( fd_cstr_printf_check( (char *)event.args_json, sizeof(event.args_json), &event.args_json_len, "{\"authorized_voter\":\"%s\"}", authorized_voter ) );
877 :
878 0 : if( FD_UNLIKELY( !ctx->tower_av_keyswitch ) ) {
879 0 : FD_LOG_WARNING(( "add-authorized-voter is not supported under Alpenglow." ));
880 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_UNSUPPORTED );
881 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_UNSUPPORTED );
882 0 : return;
883 0 : }
884 :
885 0 : uchar public_key[ 32UL ];
886 0 : fd_ed25519_public_from_private( public_key, req->keypair, ctx->sha512 );
887 0 : if( FD_UNLIKELY( memcmp( public_key, req->keypair+32UL, 32UL ) ) ) {
888 0 : FD_LOG_WARNING(( "add-authorized-voter failed: public key in key file does not match private key" ));
889 0 : report_admin_command_custom_result( &event, "keypair_mismatch" );
890 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADD_AUTHORIZED_VOTER_RESULT_KEYPAIR_MISMATCH );
891 0 : return;
892 0 : }
893 :
894 0 : ulong result = FD_ADMINCTL_RESULT_SUCCESS;
895 0 : ulong state = FD_ADD_AUTH_VOTER_STATE_UNLOCKED;
896 0 : for(;;) {
897 0 : poll_add_authorized_voter( ctx, &state, req->keypair, &result );
898 0 : if( FD_UNLIKELY( state==FD_ADD_AUTH_VOTER_STATE_UNLOCKED ) ) break;
899 0 : }
900 :
901 0 : switch( result ) {
902 0 : case FD_ADMINCTL_RESULT_SUCCESS:
903 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_SUCCESS );
904 0 : break;
905 0 : case FD_ADD_AUTHORIZED_VOTER_RESULT_MAX_AUTH_VOTERS:
906 0 : report_admin_command_custom_result( &event, "max_authorized_voters" );
907 0 : break;
908 0 : case FD_ADD_AUTHORIZED_VOTER_RESULT_DUPLICATE_AUTH_VOTER:
909 0 : report_admin_command_custom_result( &event, "duplicate_authorized_voter" );
910 0 : break;
911 0 : default:
912 0 : FD_LOG_ERR(( "unexpected add-authorized-voter result %lu", result ));
913 0 : }
914 0 : fd_adminctl_complete( adminctl, slot_idx, result );
915 0 : }
916 :
917 : static void
918 : snapshot_create( fd_admin_tile_ctx_t * ctx,
919 : fd_stem_context_t * stem,
920 : ulong slot_idx,
921 : void const * payload,
922 0 : ulong payload_sz ) {
923 :
924 0 : fd_adminctl_t * adminctl = ctx->adminctl;
925 0 : fd_event_admin_command_t event = prepare_admin_command( FD_EVENT_ADMIN_COMMAND_TYPE_SNAPSHOT_CREATE, payload, payload_sz );
926 :
927 0 : if( FD_UNLIKELY( payload_sz!=sizeof(fd_adminctl_snap_create_t) ) ) {
928 0 : FD_LOG_WARNING(( "unexpected adminctl snapshot-create payload_sz %lu", payload_sz ));
929 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
930 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
931 0 : return;
932 0 : }
933 0 : fd_adminctl_snap_create_t const * req = fd_type_pun_const( payload );
934 0 : if( FD_UNLIKELY( req->version!=FD_ADMINCTL_SNAP_CREATE_PAYLOAD_VERSION ) ) {
935 0 : FD_LOG_WARNING(( "unsupported adminctl snapshot-create payload version %lu", req->version ));
936 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_VERSION_MISMATCH );
937 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH );
938 0 : return;
939 0 : }
940 0 : ulong target_slot = req->slot;
941 0 : FD_TEST( fd_cstr_printf_check( (char *)event.args_json, sizeof(event.args_json), &event.args_json_len, "{\"target_slot\":%lu}", target_slot ) );
942 :
943 0 : if( FD_UNLIKELY( ctx->replay_out_idx==ULONG_MAX ) ) {
944 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but admin tile has no replay command link" ));
945 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_UNSUPPORTED );
946 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_UNSUPPORTED );
947 0 : return;
948 0 : }
949 :
950 0 : if( FD_UNLIKELY( ctx->snap_create_slot_idx!=ULONG_MAX ) ) {
951 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but another snapshot-create command is pending replay response" ));
952 0 : report_admin_command_custom_result( &event, "busy" );
953 0 : fd_adminctl_complete( adminctl, slot_idx, FD_SNAPSHOT_CREATE_RESULT_BUSY );
954 0 : return;
955 0 : }
956 :
957 0 : ulong ctl = fd_frag_meta_ctl( FD_ADMINCTL_CMD_SNAP_CREATE, 0, 0, 0 );
958 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
959 0 : fd_stem_publish( stem, ctx->replay_out_idx, target_slot, 0UL, 0UL, ctl, 0UL, tspub );
960 0 : ctx->snap_create_slot_idx = slot_idx;
961 0 : ctx->snap_create_target_slot = target_slot;
962 0 : ctx->snap_create_start_time = event.start_time;
963 0 : }
964 :
965 : static void
966 : snapshot_create_response( fd_admin_tile_ctx_t * ctx,
967 : ulong sig,
968 0 : ulong ctl ) {
969 :
970 0 : if( FD_UNLIKELY( fd_frag_meta_ctl_orig( ctl )!=FD_ADMINCTL_CMD_SNAP_CREATE ) ) {
971 0 : FD_LOG_ERR(( "unexpected replay admin response orig %lu", fd_frag_meta_ctl_orig( ctl ) ));
972 0 : }
973 :
974 0 : fd_event_admin_command_t event = {
975 0 : .type = FD_EVENT_ADMIN_COMMAND_TYPE_SNAPSHOT_CREATE,
976 0 : .start_time = ctx->snap_create_start_time,
977 0 : .payload_version = FD_ADMINCTL_SNAP_CREATE_PAYLOAD_VERSION,
978 0 : .has_payload_version = 1,
979 0 : .payload_size = sizeof(fd_adminctl_snap_create_t),
980 0 : };
981 0 : FD_TEST( fd_cstr_printf_check( (char *)event.args_json, sizeof(event.args_json), &event.args_json_len, "{\"target_slot\":%lu}", ctx->snap_create_target_slot ) );
982 0 : switch( sig ) {
983 0 : case FD_ADMINCTL_RESULT_SUCCESS:
984 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_SUCCESS );
985 0 : break;
986 0 : case FD_SNAPSHOT_CREATE_RESULT_BUSY:
987 0 : report_admin_command_custom_result( &event, "busy" );
988 0 : break;
989 0 : case FD_ADMINCTL_RESULT_UNSUPPORTED:
990 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_UNSUPPORTED );
991 0 : break;
992 0 : case FD_SNAPSHOT_CREATE_RESULT_NOT_READY:
993 0 : report_admin_command_custom_result( &event, "not_ready" );
994 0 : break;
995 0 : case FD_SNAPSHOT_CREATE_RESULT_SLOT_IN_PAST:
996 0 : report_admin_command_custom_result( &event, "slot_in_past" );
997 0 : break;
998 0 : default:
999 0 : FD_LOG_ERR(( "unexpected snapshot-create result %lu", sig ));
1000 0 : }
1001 0 : fd_adminctl_complete( ctx->adminctl, ctx->snap_create_slot_idx, sig );
1002 0 : ctx->snap_create_slot_idx = ULONG_MAX;
1003 0 : ctx->snap_create_target_slot = 0UL;
1004 0 : ctx->snap_create_start_time = 0UL;
1005 0 : }
1006 :
1007 : /* Removing all authorized voters from the validator is the inverse of
1008 : add-authorized-voter, and must be done in the opposite order. When
1009 : adding, the sign tile is updated before the tower tile so that the
1010 : tower never asks the sign tile to sign a vote with an authority index
1011 : the sign tile does not yet know about. When removing, the tower tile
1012 : must be cleared before the sign tiles, so that the tower stops
1013 : referencing an authorized voter index before the sign tile drops the
1014 : corresponding key.
1015 :
1016 : Clearing the tower map prevents new vote transactions from
1017 : referencing a removed voter, but transactions already published to
1018 : TxSend may still do so. The tower therefore reports its final output
1019 : sequence after draining its local publish queue. TxSend processes
1020 : every tower message through that sequence and synchronously waits for
1021 : each signing response before acknowledging the drain. Only then is
1022 : it safe to clear the sign tiles. All transitions are linear and in
1023 : forward order.
1024 :
1025 : Unlike add-authorized-voter, removal cannot fail on the tile side: it
1026 : is unconditional and idempotent (clearing an empty set succeeds). */
1027 :
1028 : /* State 0: UNLOCKED
1029 : The validator is not currently in the process of switching keys. */
1030 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED (0UL)
1031 :
1032 : /* State 1: LOCKED
1033 : Some client to the validator has requested to remove all authorized
1034 : voters. To do so, it acquired an exclusive lock on the validator to
1035 : prevent the removal potentially being interleaved with another
1036 : client. */
1037 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_LOCKED (1UL)
1038 :
1039 : /* State 2: TOWER_TILE_REQUESTED
1040 : The tower tile has been notified to clear its authorized voter set.
1041 : It is cleared first so it stops preparing vote transactions with any
1042 : authorized voter before the sign tiles drop the keys. */
1043 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_REQUESTED (2UL)
1044 :
1045 : /* State 3: TOWER_TILE_CLEARED
1046 : The tower tile confirmed it cleared its authorized voter map. At
1047 : this point the validator will only prepare vote transactions signed
1048 : by the identity key. */
1049 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_CLEARED (3UL)
1050 :
1051 : /* State 4: TXSEND_FLUSH_REQUESTED
1052 : TxSend has been notified to process every tower message through the
1053 : sequence at which the tower stopped producing votes. */
1054 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED (4UL)
1055 :
1056 : /* State 5: TXSEND_FLUSHED
1057 : TxSend confirmed that all vote transactions which could reference an
1058 : authorized voter have finished signing. */
1059 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED (5UL)
1060 :
1061 : /* State 6: SIGN_TILE_REQUESTED
1062 : All sign tiles have been notified to clear their authorized voter
1063 : keys. */
1064 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_REQUESTED (6UL)
1065 :
1066 : /* State 7: SIGN_TILE_CLEARED
1067 : All sign tiles confirmed they cleared (and securely zeroed) their
1068 : authorized voter keys. */
1069 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_CLEARED (7UL)
1070 :
1071 : /* State 8: UNLOCK_REQUESTED
1072 : The client requests that the tower tile release the lock. */
1073 0 : #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCK_REQUESTED (8UL)
1074 :
1075 : static void
1076 : poll_remove_all_authorized_voters( fd_admin_tile_ctx_t * ctx,
1077 0 : ulong * state ) {
1078 0 : fd_keyswitch_t * tower = ctx->tower_av_keyswitch;
1079 :
1080 0 : switch( *state ) {
1081 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED: {
1082 0 : if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &tower->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
1083 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_LOCKED;
1084 0 : FD_LOG_INFO(( "Locking authorized voter set for authorized voter update..." ));
1085 0 : } else {
1086 : /* keyswitch changes should be guarded and ordered by adminctl.
1087 : If the keyswitch is in a locked state means there is
1088 : unexpected process state and the validator should crash. */
1089 0 : FD_LOG_CRIT(( "keyswitch is in a locked state but should be unlocked" ));
1090 0 : }
1091 0 : break;
1092 0 : }
1093 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_LOCKED: {
1094 0 : tower->param = FD_KEYSWITCH_PARAM_AV_CLEAR;
1095 0 : FD_COMPILER_MFENCE();
1096 0 : tower->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
1097 0 : FD_COMPILER_MFENCE();
1098 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_REQUESTED;
1099 0 : FD_LOG_INFO(( "Requesting tower tile to clear authorized voter key set..." ));
1100 0 : break;
1101 0 : }
1102 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_REQUESTED: {
1103 0 : if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
1104 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_CLEARED;
1105 0 : FD_LOG_INFO(( "Tower tile authorized voter key set cleared..." ));
1106 0 : } else {
1107 0 : FD_SPIN_PAUSE();
1108 0 : }
1109 0 : break;
1110 0 : }
1111 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TOWER_TILE_CLEARED: {
1112 0 : fd_keyswitch_t * txsend = ctx->txsend_av_keyswitch;
1113 0 : FD_COMPILER_MFENCE();
1114 0 : txsend->param = tower->result;
1115 0 : FD_COMPILER_MFENCE();
1116 0 : txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
1117 0 : FD_COMPILER_MFENCE();
1118 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED;
1119 0 : FD_LOG_INFO(( "Requesting TxSend drain in-flight authorized voter signing requests..." ));
1120 0 : break;
1121 0 : }
1122 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED: {
1123 0 : if( FD_LIKELY( ctx->txsend_av_keyswitch->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
1124 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED;
1125 0 : FD_LOG_INFO(( "TxSend authorized voter signing requests drained..." ));
1126 0 : } else {
1127 0 : FD_SPIN_PAUSE();
1128 0 : }
1129 0 : break;
1130 0 : }
1131 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED: {
1132 0 : for( ulong i=0UL; i<ctx->sign_av_keyswitch_cnt; i++ ) {
1133 0 : fd_keyswitch_t * sign = ctx->sign_av_keyswitch[ i ];
1134 0 : sign->param = FD_KEYSWITCH_PARAM_AV_CLEAR;
1135 0 : FD_COMPILER_MFENCE();
1136 0 : sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
1137 0 : FD_COMPILER_MFENCE();
1138 0 : }
1139 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_REQUESTED;
1140 0 : FD_LOG_INFO(( "Requesting all sign tiles to clear authorized voter key set..." ));
1141 0 : break;
1142 0 : }
1143 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_REQUESTED: {
1144 0 : int all_cleared = 1;
1145 0 : for( ulong i=0UL; i<ctx->sign_av_keyswitch_cnt; i++ ) {
1146 0 : fd_keyswitch_t * sign = ctx->sign_av_keyswitch[ i ];
1147 0 : if( FD_UNLIKELY( sign->state!=FD_KEYSWITCH_STATE_COMPLETED ) ) {
1148 0 : all_cleared = 0;
1149 0 : break;
1150 0 : }
1151 0 : }
1152 :
1153 0 : if( FD_LIKELY( all_cleared ) ) *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_CLEARED;
1154 0 : else FD_SPIN_PAUSE();
1155 0 : break;
1156 0 : }
1157 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_CLEARED: {
1158 0 : tower->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
1159 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCK_REQUESTED;
1160 0 : FD_LOG_INFO(( "Requesting an unlock of the authorized voter key set..." ));
1161 0 : break;
1162 0 : }
1163 0 : case FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCK_REQUESTED: {
1164 0 : if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_UNLOCKED ) ) {
1165 0 : *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED;
1166 0 : FD_LOG_INFO(( "Authorized voter key set unlocked..." ));
1167 0 : } else {
1168 0 : FD_SPIN_PAUSE();
1169 0 : }
1170 0 : break;
1171 0 : }
1172 0 : default: {
1173 0 : FD_LOG_CRIT(( "Unexpected remove-all-authorized-voters state %lu", *state ));
1174 0 : }
1175 0 : }
1176 0 : }
1177 :
1178 : static void
1179 : remove_all_authorized_voters( fd_admin_tile_ctx_t * ctx,
1180 : ulong slot_idx,
1181 : void * data,
1182 0 : ulong data_sz ) {
1183 :
1184 0 : fd_adminctl_t * adminctl = ctx->adminctl;
1185 0 : fd_event_admin_command_t event = prepare_admin_command( FD_EVENT_ADMIN_COMMAND_TYPE_REMOVE_ALL_AUTHORIZED_VOTERS, data, data_sz );
1186 :
1187 0 : if( FD_UNLIKELY( data_sz<sizeof(ulong) ) ) {
1188 0 : FD_LOG_WARNING(( "adminctl remove-all-authorized-voters payload too small: %lu", data_sz ));
1189 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
1190 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
1191 0 : return;
1192 0 : }
1193 :
1194 0 : ulong version = FD_LOAD( ulong, data );
1195 0 : if( FD_UNLIKELY( version!=FD_ADMINCTL_REMOVE_ALL_AUTH_VOTERS_PAYLOAD_VERSION ) ) {
1196 0 : FD_LOG_WARNING(( "unsupported adminctl remove-all-authorized-voters payload version %lu", version ));
1197 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_VERSION_MISMATCH );
1198 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH );
1199 0 : return;
1200 0 : }
1201 :
1202 0 : if( FD_UNLIKELY( data_sz!=sizeof(fd_adminctl_remove_all_auth_voters_t) ) ) {
1203 0 : FD_LOG_WARNING(( "unexpected adminctl remove-all-authorized-voters payload_sz %lu", data_sz ));
1204 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH );
1205 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH );
1206 0 : return;
1207 0 : }
1208 :
1209 0 : if( FD_UNLIKELY( !ctx->tower_av_keyswitch ) ) {
1210 0 : FD_LOG_WARNING(( "remove-all-authorized-voters is not supported under Alpenglow." ));
1211 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_UNSUPPORTED );
1212 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_UNSUPPORTED );
1213 0 : return;
1214 0 : }
1215 :
1216 0 : ulong state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED;
1217 0 : for(;;) {
1218 0 : poll_remove_all_authorized_voters( ctx, &state );
1219 0 : if( FD_UNLIKELY( state==FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED ) ) break;
1220 0 : }
1221 :
1222 0 : report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_SUCCESS );
1223 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_SUCCESS );
1224 0 : }
1225 :
1226 : static inline void FD_FN_SENSITIVE
1227 : after_credit( fd_admin_tile_ctx_t * ctx,
1228 : fd_stem_context_t * stem,
1229 : int * opt_poll_in,
1230 0 : int * charge_busy ) {
1231 :
1232 0 : fd_adminctl_t * adminctl = ctx->adminctl;
1233 0 : ulong slot_idx = ULONG_MAX;
1234 0 : void * payload = NULL;
1235 0 : ulong payload_sz = 0UL;
1236 :
1237 0 : ulong cmd_id = fd_adminctl_poll( adminctl, &slot_idx, &payload, &payload_sz );
1238 0 : switch( cmd_id ) {
1239 0 : case FD_ADMINCTL_CMD_IDLE:
1240 0 : break;
1241 0 : case FD_ADMINCTL_CMD_ADD_AUTH_VOTER:
1242 0 : add_authorized_voter( ctx, slot_idx, payload, payload_sz );
1243 0 : *charge_busy = 1;
1244 0 : break;
1245 0 : case FD_ADMINCTL_CMD_SET_IDENTITY:
1246 0 : set_identity( ctx, slot_idx, payload, payload_sz );
1247 0 : *charge_busy = 1;
1248 0 : break;
1249 0 : case FD_ADMINCTL_CMD_REMOVE_ALL_AUTH_VOTERS:
1250 0 : remove_all_authorized_voters( ctx, slot_idx, payload, payload_sz );
1251 0 : *charge_busy = 1;
1252 0 : break;
1253 0 : case FD_ADMINCTL_CMD_GET_IDENTITY:
1254 0 : get_identity( ctx, slot_idx, payload, payload_sz );
1255 0 : *charge_busy = 1;
1256 0 : break;
1257 0 : case FD_ADMINCTL_CMD_SNAP_CREATE:
1258 0 : snapshot_create( ctx, stem, slot_idx, payload, payload_sz );
1259 0 : *charge_busy = 1;
1260 0 : *opt_poll_in = 0;
1261 0 : break;
1262 0 : default:
1263 0 : FD_LOG_WARNING(( "unexpected adminctl cmd %lu", cmd_id ));
1264 0 : fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_UNKNOWN_COMMAND );
1265 0 : }
1266 0 : }
1267 :
1268 : static void
1269 : during_frag( fd_admin_tile_ctx_t * ctx,
1270 : ulong in_idx FD_PARAM_UNUSED,
1271 : ulong seq FD_PARAM_UNUSED,
1272 : ulong sig,
1273 : ulong chunk FD_PARAM_UNUSED,
1274 : ulong sz FD_PARAM_UNUSED,
1275 0 : ulong ctl ) {
1276 0 : if( FD_UNLIKELY( ctx->snap_create_slot_idx==ULONG_MAX ) ) {
1277 0 : FD_LOG_ERR(( "unexpected replay snapshot-create response with no pending adminctl command" ));
1278 0 : return;
1279 0 : }
1280 0 : snapshot_create_response( ctx, sig, ctl );
1281 0 : }
1282 :
1283 : static ulong
1284 : populate_allowed_seccomp( fd_topo_t const * topo FD_PARAM_UNUSED,
1285 : fd_topo_tile_t const * tile FD_PARAM_UNUSED,
1286 : ulong out_cnt,
1287 0 : struct sock_filter * out ) {
1288 :
1289 0 : populate_sock_filter_policy_fd_admin_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
1290 0 : return sock_filter_policy_fd_admin_tile_instr_cnt;
1291 0 : }
1292 :
1293 : static ulong
1294 : populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED,
1295 : fd_topo_tile_t const * tile FD_PARAM_UNUSED,
1296 : ulong out_fds_cnt,
1297 0 : int * out_fds ) {
1298 :
1299 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
1300 :
1301 0 : ulong out_cnt = 0UL;
1302 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
1303 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
1304 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1305 0 : return out_cnt;
1306 0 : }
1307 :
1308 0 : #define STEM_BURST (1UL)
1309 0 : #define STEM_LAZY ((long)1e6) /* 1ms */
1310 :
1311 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_admin_tile_ctx_t
1312 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_admin_tile_ctx_t)
1313 :
1314 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
1315 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
1316 :
1317 : #include "../../disco/stem/fd_stem.c"
1318 :
1319 : static ulong
1320 0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
1321 0 : return sizeof(fd_event_admin_command_t);
1322 0 : }
1323 :
1324 : fd_topo_run_tile_t fd_tile_admin = {
1325 : .name = "admin",
1326 : .max_event_sz = max_event_sz,
1327 : .populate_allowed_seccomp = populate_allowed_seccomp,
1328 : .populate_allowed_fds = populate_allowed_fds,
1329 : .scratch_align = scratch_align,
1330 : .scratch_footprint = scratch_footprint,
1331 : .privileged_init = privileged_init,
1332 : .unprivileged_init = unprivileged_init,
1333 : .run = stem_run,
1334 : };
|