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