Line data Source code
1 : #define _GNU_SOURCE
2 : #include "../../shared/fd_config.h"
3 : #include "../../shared/fd_action.h"
4 :
5 : #include <stdlib.h>
6 : #include <unistd.h>
7 : #include "../../platform/fd_cap_chk.h"
8 : #include "../../../disco/keyguard/fd_keyswitch.h"
9 : #include "../../../disco/keyguard/fd_keyload.h"
10 : #include "../../../disco/topo/fd_topo.h"
11 :
12 : #include <strings.h>
13 : #include <unistd.h>
14 : #include <sys/resource.h>
15 :
16 : /* The process of switching identity of the validator is somewhat
17 : involved, to prevent it from producing torn data (for example,
18 : a block where half the shreds are signed by one private key, and half
19 : are signed by another).
20 :
21 : The process of switching is a state machine that progresses linearly
22 : through each of the states. Generally, no transitions are allowed
23 : except direct forward steps, except in emergency recovery cases an
24 : operator can force the state back to unlocked.
25 :
26 : The states follow, in order. */
27 :
28 : /* State 0: UNLOCKED.
29 : The validator is not currently in the process of switching keys. */
30 0 : #define FD_SET_IDENTITY_STATE_UNLOCKED (0UL)
31 :
32 : /* State 1: LOCKED
33 : Some client to the validator has requested a key switch. To do so,
34 : it acquired an exclusive lock on the validator to prevent the
35 : switch potentially being interleaved with another client. */
36 0 : #define FD_SET_IDENTITY_STATE_LOCKED (1UL)
37 :
38 : /* State 2: LEADER_HALT_REQUESTED
39 : The first step in the key switch process is to pause the leader
40 : pipeline of the validator, preventing us from becoming leader, but
41 : finishing any currently in progress leader slot if there is one.
42 : While in this state, the validator is waiting for the leader
43 : pipeline to confirm that it has paused production, and is no longer
44 : leader.
45 :
46 : In Firedancer, this halt request goes to the Replay tile, which
47 : causes the tile to switch the identity key it uses to determine the
48 : identity's balance as well as when the validator is the leader.
49 : After the leader pipeline has been halted, the validator will no
50 : longer become a leader until the switch has been completed. */
51 0 : #define FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED (2UL)
52 :
53 : /* State 3: LEADER_HALTED
54 : The Replay tile has confirmed that it has halted the leader
55 : pipeline, and the validator is no longer leader. No more blocks
56 : will be produced until it is unhalted. In addition, the Replay
57 : tile has switched its own identity key.
58 :
59 : At this point, we also have the guarantee that there are no more
60 : outstanding shreds that have to be signed with the old key. Any
61 : tiles related to the leader pipeline that rely on the identity key
62 : will not be used. */
63 0 : #define FD_SET_IDENTITY_STATE_LEADER_HALTED (3UL)
64 :
65 : /* State 4: REPLAY_HALT_REQUESTED
66 : Repair, Gossip, and Tower tiles will stop sending requests
67 : downstream to the sign tile. This is done to avoid any mismatches
68 : with the identity key. Their identity keys will be switched after
69 : this step. These tiles all use the identity key to make forward
70 : progress on non-leader pipeline replay.
71 :
72 : These tiles use the identity key to populate messages which are
73 : signed by the sign tile:
74 : (a) Repair. The repair tile uses the identity key as part of the
75 : repair protocol. The identity key is included in and used
76 : for signing requests. Because Repair uses an asnychronous
77 : signing mechanism, Repair will first wait until all
78 : outstanding sign requests have been received back from the
79 : sign tile before halting any new signing requests.
80 : (b) Gossip. The gossip tile sends out ContactInfo messages with
81 : our identity key, and also uses the identity key to sign
82 : outgoing gossip messages.
83 : (c) Tower. The tower tiles uses the identity key to generate
84 : vote transactions which are sent to the send tile. These
85 : vote transactions are then signed downstream by the TxSend
86 : tile instead of having its own keyguard client. */
87 0 : #define FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED (4UL)
88 :
89 : /* State 5: REPLAY_HALTED
90 : Repair, Gossip, and Tower are no longer sending requests to the
91 : sign tile. Replay can keep progressing at this point. However,
92 : the tower tile may have an in-flight vote transaction to the TxSend
93 : tile that corresponds to the old identity key. */
94 0 : #define FD_SET_IDENTITY_STATE_REPLAY_HALTED (5UL)
95 :
96 : /* State 6: SEND_FLUSH_REQUESTED
97 : Once the Tower tile has updated its identity key and stopped
98 : sending vote transactions to the TxSend tile, any in-flight vote
99 : transactions for the old identity key must be flushed to avoid
100 : being badly signed. We also know that Tower will send no more
101 : vote transactions to the TxSend tile.
102 :
103 : The TxSend tile is flushed by telling it the last sequence number
104 : the Tower tile has produced for an outgoing vote tansaction at the
105 : time it was halted. Once the TxSend tile has processed all vote
106 : transactions up to and including that sequence number, it will
107 : switch it's own identity key. There is a guarantee that the TxSend
108 : tile will not request to sign any vote transactions until it is
109 : unhalted. At this point, the TxSend tile will stop receving any
110 : new frags from the Net tile. The reason for this is to avoid any
111 : QUIC callbacks that invoke key signing. */
112 0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED (6UL)
113 :
114 : /* State 7: TXSEND_FLUSHED
115 : The TxSend tile confirms that it has seen and processed all votes
116 : up to and including the last sequence number produced by the Tower
117 : tile at the time it was halted. The TxSend tile also switches its
118 : own identity key which is used for signing votes and establishing
119 : a QUIC connection. The TxSend tile is now no longer receiving any
120 : new frags from the Net tile. */
121 0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSHED (7UL)
122 :
123 : /* State 8: ALL_SWITCH_REQUESTED
124 : The client now requests that all other tiles which consume the
125 : identity key in some way switch to the new key. The leader
126 : pipeline is still halted, although it doesn't strictly need to be,
127 : since outgoing shreds have been flushed. This is done to keep the
128 : control flow simpler. The sign tile is switched first to avoid any
129 : potential mismatches with the identity key.
130 :
131 : The other tiles using the identity key are:
132 : (a) Sign. The sign tile is responsible for holding the private
133 : key and servicing signing requests from other tiles.
134 : (b) GUI. The GUI shows the validator identity key to the user,
135 : and uses the key to determine which blocks are ours for
136 : highlighting on the frontend.
137 : (c) Bundle. The validator must authenticate to any connected
138 : bundle server with the identity key to prove it is on the
139 : leader schedule.
140 : (d) Gossvf. The gossvf tile uses the identity key to detect
141 : duplicate running instances of the same validator node as
142 : well as other message handling.
143 : (e) Shred. The shred tile uses the identity key to determine the
144 : position of the validator in the Turbine tree and to sign
145 : outgoing shreds.
146 : (f) Event. Outgoing events to the event server are signed with
147 : the identity key to authenticate the sender. */
148 :
149 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED (8UL)
150 :
151 : /* State 9: ALL_SWITCHED
152 : All remaining tiles that use the identity key have confirmed that
153 : they have switched to the new key. The validator is now fully
154 : switched over. */
155 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCHED (9UL)
156 :
157 : /* State 10: REPLAY_UNHALT_REQUESTED
158 : Now that all of the tiles are using the switched identity key, the
159 : tiles that rely on the sign tile can be unhalted. These are the
160 : same tiles from REPLAY_HALT_REQUESTED. */
161 0 : #define FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED (10UL)
162 :
163 : /* State 11: REPLAY_UNHALTED
164 : All tiles that rely on the sign tile have been unhalted and now the
165 : validator can resume making progress on replay. */
166 0 : #define FD_SET_IDENTITY_STATE_REPLAY_UNHALTED (11UL)
167 :
168 : /* State 12: LEADER_UNHALT_REQUESTED
169 : The final state, now that all tiles have switched, the leader
170 : pipeline can be unblocked and the validator can resume producing
171 : blocks. The next state once the Replay tile confirms the leader
172 : pipeline is unlocked, is UNLOCKED. */
173 0 : #define FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED (12UL)
174 :
175 : void
176 : set_identity_cmd_perm( args_t * args FD_PARAM_UNUSED,
177 : fd_cap_chk_t * chk,
178 0 : config_t const * config FD_PARAM_UNUSED ) {
179 : /* 5 huge pages for the key storage area */
180 0 : ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
181 0 : fd_cap_chk_raise_rlimit( chk, "set-identity", RLIMIT_MEMLOCK, mlock_limit, "call `rlimit(2)` to increase `RLIMIT_MEMLOCK` so all memory can be locked with `mlock(2)`" );
182 0 : }
183 :
184 : static fd_keyswitch_t *
185 : find_keyswitch( fd_topo_t const * topo,
186 0 : char const * tile_name ) {
187 0 : ulong tile_idx = fd_topo_find_tile( topo, tile_name, 0UL );
188 0 : FD_TEST( tile_idx!=ULONG_MAX );
189 0 : FD_TEST( topo->tiles[ tile_idx ].id_keyswitch_obj_id!=ULONG_MAX );
190 :
191 0 : fd_keyswitch_t * keyswitch = fd_topo_obj_laddr( topo, topo->tiles[ tile_idx ].id_keyswitch_obj_id );
192 0 : FD_TEST( keyswitch );
193 0 : return keyswitch;
194 0 : }
195 :
196 : static void FD_FN_SENSITIVE
197 : poll_keyswitch( fd_topo_t * topo,
198 : ulong * state,
199 : ulong * halted_seq,
200 : uchar * keypair,
201 : int require_tower,
202 0 : int force_lock ) {
203 0 : switch( *state ) {
204 0 : case FD_SET_IDENTITY_STATE_UNLOCKED: {
205 : /* First update replay's keyswitch from unlocked to locked. */
206 0 : fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
207 0 : if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &replay->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
208 0 : *state = FD_SET_IDENTITY_STATE_LOCKED;
209 0 : FD_LOG_INFO(( "Locking validator identity for key switch..." ));
210 0 : } else {
211 0 : if( FD_UNLIKELY( force_lock ) ) {
212 0 : *state = FD_SET_IDENTITY_STATE_LOCKED;
213 0 : FD_LOG_WARNING(( "Another process was changing keys, but `--force` supplied. Forcing lock on validator identity for key switch..." ));
214 0 : } else {
215 0 : FD_LOG_ERR(( "Cannot set-identity because Firedancer is already in the process of switching keys. If you are not currently "
216 0 : "changing the identity, it might be because an identity change was abandoned. To recover, run the `set-identity` "
217 0 : "command again with the `--force` argument." ));
218 0 : }
219 0 : }
220 0 : break;
221 0 : }
222 0 : case FD_SET_IDENTITY_STATE_LOCKED: {
223 0 : fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
224 0 : memcpy( replay->bytes, keypair+32UL, 32UL );
225 :
226 0 : FD_COMPILER_MFENCE();
227 0 : replay->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
228 0 : FD_COMPILER_MFENCE();
229 0 : *state = FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED;
230 0 : FD_LOG_INFO(( "Pausing leader pipeline for key switch..." ));
231 0 : break;
232 0 : }
233 0 : case FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED: {
234 0 : fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
235 0 : if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
236 0 : fd_memzero_explicit( replay->bytes, 64UL );
237 0 : FD_COMPILER_MFENCE();
238 0 : *halted_seq = replay->result;
239 0 : *state = FD_SET_IDENTITY_STATE_LEADER_HALTED;
240 0 : FD_LOG_INFO(( "Leader pipeline successfully paused..." ));
241 0 : } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
242 0 : FD_SPIN_PAUSE();
243 0 : } else {
244 0 : FD_LOG_ERR(( "Unexpected keyswitch state %lu", replay->state ));
245 0 : }
246 0 : break;
247 0 : }
248 0 : case FD_SET_IDENTITY_STATE_LEADER_HALTED: {
249 : /* Now we have to flush any in-flight and block requests from the
250 : repair, gossip, send, and tower tiles that need to be signed.
251 :
252 : TODO: Tower currently doesn't support running off of a tower
253 : file. When support for a tower file is added, the tower file
254 : will need to be swapped and synced with the local state of the
255 : tower. The security sandbox implications of adding another
256 : file descriptor need to be considered. */
257 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
258 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
259 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
260 0 : if( strcmp( tile->name, "repair" ) &&
261 0 : strcmp( tile->name, "gossip" ) &&
262 0 : strcmp( tile->name, "tower" ) ) {
263 0 : continue;
264 0 : }
265 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
266 :
267 0 : if( !strcmp( tile->name, "tower" ) ) tile_ks->param = !!require_tower;
268 :
269 0 : memcpy( tile_ks->bytes, keypair+32UL, 32UL );
270 0 : FD_COMPILER_MFENCE();
271 0 : tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
272 0 : FD_COMPILER_MFENCE();
273 0 : }
274 0 : *state = FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED;
275 0 : FD_LOG_INFO(( "Requesting to halt all signers..." ));
276 0 : break;
277 0 : }
278 0 : case FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED: {
279 0 : int all_switched = 1;
280 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
281 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
282 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
283 0 : if( strcmp( tile->name, "repair" )!=0 &&
284 0 : strcmp( tile->name, "gossip" )!=0 &&
285 0 : strcmp( tile->name, "tower" )!=0 ) {
286 0 : continue;
287 0 : }
288 :
289 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
290 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
291 0 : all_switched = 0UL;
292 0 : break;
293 0 : }
294 0 : }
295 0 : if( FD_LIKELY( all_switched ) ) {
296 0 : FD_LOG_INFO(( "All successfully switched identity key..." ));
297 0 : *state = FD_SET_IDENTITY_STATE_REPLAY_HALTED;
298 0 : } else {
299 0 : FD_SPIN_PAUSE();
300 0 : }
301 0 : break;
302 0 : }
303 0 : case FD_SET_IDENTITY_STATE_REPLAY_HALTED: {
304 0 : ulong tower_halted_seq = find_keyswitch( topo, "tower" )->result;
305 0 : fd_keyswitch_t * txsend = find_keyswitch( topo, "txsend" );
306 0 : txsend->param = tower_halted_seq;
307 0 : memcpy( txsend->bytes, keypair+32UL, 32UL );
308 0 : FD_COMPILER_MFENCE();
309 0 : txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
310 0 : FD_COMPILER_MFENCE();
311 :
312 0 : *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED;
313 0 : break;
314 0 : }
315 0 : case FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED: {
316 0 : fd_keyswitch_t * txsend = find_keyswitch( topo, "txsend" );
317 0 : if( FD_LIKELY( txsend->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
318 0 : fd_memzero_explicit( txsend->bytes, 64UL );
319 0 : FD_COMPILER_MFENCE();
320 0 : *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSHED;
321 0 : } else {
322 0 : FD_SPIN_PAUSE();
323 0 : }
324 0 : break;
325 0 : }
326 0 : case FD_SET_IDENTITY_STATE_TXSEND_FLUSHED: {
327 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
328 0 : fd_topo_tile_t * tile = &topo->tiles[ i ];
329 0 : if( strcmp( tile->name, "sign" ) ) continue;
330 0 : fd_keyswitch_t * sign = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
331 0 : memcpy( sign->bytes, keypair, 64UL );
332 0 : FD_COMPILER_MFENCE();
333 0 : sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
334 0 : FD_COMPILER_MFENCE();
335 0 : }
336 :
337 0 : FD_COMPILER_MFENCE();
338 0 : fd_memzero_explicit( keypair, 32UL ); /* Private key no longer needed in this process */
339 0 : FD_COMPILER_MFENCE();
340 :
341 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
342 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
343 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ||
344 0 : !strcmp( topo->tiles[ i ].name, "replay" ) ||
345 0 : !strcmp( topo->tiles[ i ].name, "shred" ) ||
346 0 : !strcmp( topo->tiles[ i ].name, "repair" ) ||
347 0 : !strcmp( topo->tiles[ i ].name, "gossip" ) ||
348 0 : !strcmp( topo->tiles[ i ].name, "txsend" ) ||
349 0 : !strcmp( topo->tiles[ i ].name, "tower" ) ) ) continue;
350 :
351 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
352 0 : memcpy( tile_ks->bytes, keypair+32UL, 32UL );
353 0 : FD_COMPILER_MFENCE();
354 0 : tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
355 0 : FD_COMPILER_MFENCE();
356 0 : }
357 :
358 0 : FD_LOG_INFO(( "Requesting all remaining tiles switch identity key..." ));
359 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED;
360 0 : break;
361 0 : }
362 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED: {
363 0 : ulong all_switched = 1UL;
364 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
365 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
366 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "replay" ) ||
367 0 : !strcmp( topo->tiles[ i ].name, "shred" ) ||
368 0 : !strcmp( topo->tiles[ i ].name, "repair" ) ||
369 0 : !strcmp( topo->tiles[ i ].name, "gossip" ) ||
370 0 : !strcmp( topo->tiles[ i ].name, "txsend" ) ||
371 0 : !strcmp( topo->tiles[ i ].name, "tower" ) ) ) continue;
372 :
373 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
374 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
375 0 : all_switched = 0UL;
376 0 : break;
377 0 : } else if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
378 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ) ) {
379 0 : FD_COMPILER_MFENCE();
380 0 : fd_memzero_explicit( tile_ks->bytes, 64UL );
381 0 : FD_COMPILER_MFENCE();
382 0 : }
383 0 : continue;
384 0 : } else {
385 0 : FD_LOG_ERR(( "Unexpected %s keyswitch state %lu", topo->tiles[ i ].name, tile_ks->state ));
386 0 : }
387 0 : }
388 :
389 0 : if( FD_LIKELY( all_switched ) ) {
390 0 : FD_LOG_INFO(( "All tiles successfully switched identity key..." ));
391 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCHED;
392 0 : } else {
393 0 : FD_SPIN_PAUSE();
394 0 : }
395 0 : break;
396 0 : }
397 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCHED: {
398 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
399 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
400 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
401 0 : if( strcmp( tile->name, "repair" ) &&
402 0 : strcmp( tile->name, "gossip" ) &&
403 0 : strcmp( tile->name, "tower" ) &&
404 0 : strcmp( tile->name, "txsend" ) ) {
405 0 : continue;
406 0 : }
407 :
408 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
409 0 : FD_COMPILER_MFENCE();
410 0 : tile_ks->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
411 0 : FD_COMPILER_MFENCE();
412 0 : }
413 :
414 0 : FD_LOG_INFO(( "Requesting to unpause signers..." ));
415 0 : *state = FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED;
416 0 : break;
417 0 : }
418 0 : case FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED: {
419 0 : int all_switched = 1;
420 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
421 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
422 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
423 0 : if( strcmp( tile->name, "repair" ) &&
424 0 : strcmp( tile->name, "gossip" ) &&
425 0 : strcmp( tile->name, "tower" ) &&
426 0 : strcmp( tile->name, "txsend" ) ) {
427 0 : continue;
428 0 : }
429 :
430 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
431 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
432 0 : all_switched = 0UL;
433 0 : break;
434 0 : }
435 0 : }
436 0 : if( FD_LIKELY( all_switched ) ) {
437 0 : FD_LOG_INFO(( "Successfully unpaused all non-leader signers..." ));
438 0 : *state = FD_SET_IDENTITY_STATE_REPLAY_UNHALTED;
439 0 : } else {
440 0 : FD_SPIN_PAUSE();
441 0 : }
442 0 : break;
443 0 : }
444 0 : case FD_SET_IDENTITY_STATE_REPLAY_UNHALTED: {
445 0 : fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
446 0 : replay->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
447 0 : FD_LOG_INFO(( "Requesting to unpause leader pipeline..." ));
448 0 : *state = FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED;
449 0 : break;
450 0 : }
451 0 : case FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED: {
452 0 : fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
453 0 : if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
454 0 : FD_LOG_INFO(( "Leader pipeline unpaused..." ));
455 0 : replay->state = FD_KEYSWITCH_STATE_UNLOCKED;
456 0 : *state = FD_SET_IDENTITY_STATE_UNLOCKED;
457 0 : } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
458 0 : FD_SPIN_PAUSE();
459 0 : } else {
460 0 : FD_LOG_ERR(( "Unexpected replay keyswitch state %lu", replay->state ));
461 0 : }
462 0 : break;
463 0 : }
464 0 : }
465 0 : }
466 :
467 : void
468 : set_identity_cmd_args( int * pargc,
469 : char *** pargv,
470 0 : args_t * args) {
471 0 : args->set_identity.require_tower = fd_env_strip_cmdline_contains( pargc, pargv, "--require-tower" );
472 0 : args->set_identity.force = fd_env_strip_cmdline_contains( pargc, pargv, "--force" );
473 :
474 0 : if( FD_UNLIKELY( *pargc<1 ) ) goto err;
475 :
476 0 : char const * path = *pargv[0];
477 0 : (*pargc)--;
478 0 : (*pargv)++;
479 :
480 0 : if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
481 0 : args->set_identity.keypair = fd_keyload_alloc_protected_pages( 1UL, 2UL );
482 0 : FD_LOG_STDOUT(( "Reading identity keypair from stdin. Press Ctrl-D when done.\n" ));
483 0 : fd_keyload_read( STDIN_FILENO, "stdin", args->set_identity.keypair );
484 0 : } else {
485 0 : args->set_identity.keypair = fd_keyload_load( path, 0 );
486 0 : }
487 :
488 0 : return;
489 :
490 0 : err:
491 0 : FD_LOG_ERR(( "Usage: firedancer set-identity <keypair> [--require-tower] [--force]" ));
492 0 : }
493 :
494 : static void FD_FN_SENSITIVE
495 : set_identity( args_t * args,
496 0 : config_t * config ) {
497 0 : uchar check_public_key[ 32 ];
498 0 : fd_sha512_t sha512[1];
499 0 : FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
500 0 : fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
501 0 : if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
502 0 : FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
503 0 : "Firedancer will not use the key pair to sign as it might leak the private key." ));
504 :
505 0 : for( ulong i=0UL; i<config->topo.obj_cnt; i++ ) {
506 0 : fd_topo_obj_t * obj = &config->topo.objs[ i ];
507 0 : if( FD_LIKELY( strcmp( obj->name, "keyswitch" ) ) ) continue;
508 :
509 0 : fd_topo_join_workspace( &config->topo, &config->topo.workspaces[ obj->wksp_id ], FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
510 0 : }
511 :
512 0 : ulong state = FD_SET_IDENTITY_STATE_UNLOCKED;
513 0 : ulong halted_seq = 0UL;
514 0 : for(;;) {
515 0 : poll_keyswitch( &config->topo, &state, &halted_seq, args->set_identity.keypair, args->set_identity.require_tower, args->set_identity.force );
516 0 : if( FD_UNLIKELY( FD_SET_IDENTITY_STATE_UNLOCKED==state ) ) break;
517 0 : }
518 :
519 0 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
520 0 : fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
521 0 : identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
522 :
523 0 : FD_LOG_NOTICE(( "Validator identity key switched to `%s`", identity_key_base58 ));
524 0 : }
525 :
526 : void
527 : set_identity_cmd_fn( args_t * args,
528 0 : config_t * config ) {
529 0 : set_identity( args, config );
530 0 : }
531 :
532 : action_t fd_action_set_identity = {
533 : .name = "set-identity",
534 : .args = set_identity_cmd_args,
535 : .fn = set_identity_cmd_fn,
536 : .require_config = 1,
537 : .perm = set_identity_cmd_perm,
538 : .description = "Change the identity of a running validator",
539 : };
|