Line data Source code
1 : #define _GNU_SOURCE
2 : #include "../../shared/fd_config.h"
3 : #include "../../shared/fd_action.h"
4 :
5 : #include "../../platform/fd_cap_chk.h"
6 : #include "../../../disco/topo/fd_topo.h"
7 : #include "../../../disco/keyguard/fd_keyswitch.h"
8 : #include "../../../disco/keyguard/fd_keyload.h"
9 :
10 : #include <strings.h>
11 : #include <unistd.h>
12 : #include <sys/resource.h>
13 :
14 : /* The process of switching identity of the validator is somewhat
15 : involved, to prevent it from producing torn data (for example,
16 : a block where half the shreds are signed by one private key, and half
17 : are signed by another).
18 :
19 : The process of switching is a state machine that progresses linearly
20 : through each of the states. Generally, no transitions are allowed
21 : except direct forward steps, except in emergency recovery cases an
22 : operator can force the state back to unlocked.
23 :
24 : The states follow, in order. */
25 :
26 : /* State 0: UNLOCKED.
27 : The validator is not currently in the process of switching keys. */
28 0 : #define FD_SET_IDENTITY_STATE_UNLOCKED (0UL)
29 :
30 : /* State 1: LOCKED
31 : Some client to the validator has requested a key switch. To do so,
32 : it acquired an exclusive lock on the validator to prevent the
33 : switch potentially being interleaved with another client. */
34 0 : #define FD_SET_IDENTITY_STATE_LOCKED (1UL)
35 :
36 : /* State 2: POH_HALT_REQUESTED
37 : The first step in the key switch process is to pause the leader
38 : pipeline of the validator, preventing us from becoming leader, but
39 : finishing any currently in progress leader slot if there is one.
40 : While in this state, the validator is waiting for the leader
41 : pipeline to confirm that it has paused production, and is no longer
42 : leader.
43 :
44 : This halt request also causes the PoH tile to switch both:
45 :
46 : (a) The identity key used by the PoH tile itself, used to
47 : determine when this validator is leader in the schedule.
48 :
49 : (b) The key used by the Agave sub-process, if running
50 : Frankendancer. The Agave key is inside a Mutex<> so it is
51 : swapped atomically across all consumers. */
52 0 : #define FD_SET_IDENTITY_STATE_POH_HALT_REQUESTED (2UL)
53 :
54 : /* State 3: POH_HALTED
55 : The PoH tile has confirmed that it has halted the leader pipeline,
56 : and the validator is no longer leader. No more blocks will be
57 : produced until it is unhalted. In addition, the PoH tile has
58 : switched both its own identity key and the Agave key. */
59 0 : #define FD_SET_IDENTITY_STATE_POH_HALTED (3UL)
60 :
61 : /* State 4: SHRED_FLUSH_REQUESTED
62 : Once the leader pipeline is halted, it must be flushed, meaning any
63 : in-flight shreds that could potentially need to be signed with the
64 : old key are signed and sent to the network. This doesn't strictly
65 : need to happen before other tiles have their key flushed, but it
66 : makes the control flow easier to understand if we do this as an
67 : explicit step.
68 :
69 : The shred tile is flushed by telling it the last sequence number
70 : the PoH tile has produced for an outgoing shred, at the time it was
71 : halted, and then waiting for the shred tile to confirm that it has
72 : seen and processed all shreds up to and including that sequence
73 : number.
74 :
75 : In addition to flushing out any in-flight shreds, this also causes
76 : the shred tile to switch the identity key it uses internally, for
77 : determining where this validator is positioned in the Turbine tree.
78 : The bundle tile is halted in parallel so it cannot request a
79 : signature while the sign tile switches keys. */
80 0 : #define FD_SET_IDENTITY_STATE_SHRED_FLUSH_REQUESTED (4UL)
81 :
82 : /* State 5: SHRED_FLUSHED
83 : The shred tile confirms that it has seen and processed all shreds
84 : up to and including the last sequence number produced by the PoH
85 : tile at the time it was halted. The shred tile has also switched
86 : its own identity key when it indicates the flush is complete. */
87 0 : #define FD_SET_IDENTITY_STATE_SHRED_FLUSHED (5UL)
88 :
89 : /* State 6: ALL_SWITCH_REQUESTED
90 : The client now requests that all other tiles which consume the
91 : identity key in some way switch to the new key. The leader
92 : pipeline is still halted, although it doesn't strictly need to be,
93 : since outgoing shreds have been flushed. This is done to keep the
94 : control flow simpler.
95 :
96 : The other tiles using the identity key are:
97 :
98 : (a) Sign. The sign tile is responsible for holding the private
99 : key.
100 : (b) GUI. The GUI shows the validator identity key to the user,
101 : and uses the key to determine which blocks are ours for
102 : highlighting on the frontend.
103 : (c) Event. Outgoing events to the event server are signed with
104 : the identity key to authenticate the sender. */
105 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED (6UL)
106 :
107 : /* State 7: ALL_SWITCHED
108 : All remaining tiles that use the identity key have confirmed that
109 : they have switched to the new key. The bundle tile has updated
110 : its identity key, but signing remains halted. */
111 0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCHED (7UL)
112 :
113 : /* State 8: BUNDLE_UNHALT_REQUESTED
114 : The bundle tile can now safely resume signing with the new key.
115 : If the bundle tile doesn't exist, skip this stage. */
116 0 : #define FD_SET_IDENTITY_STATE_BUNDLE_UNHALT_REQUESTED (8UL)
117 :
118 : /* State 9: POH_UNHALT_REQUESTED
119 : The final state, now that all tiles have switched, the leader
120 : pipeline can be unblocked and the validator can resume producing
121 : blocks. The next state once the PoH tile confirms the leader
122 : pipeline is unlocked, is UNLOCKED. */
123 0 : #define FD_SET_IDENTITY_STATE_POH_UNHALT_REQUESTED (9UL)
124 :
125 : void
126 : set_identityh_cmd_perm( args_t * args FD_PARAM_UNUSED,
127 : fd_cap_chk_t * chk,
128 0 : config_t const * config FD_PARAM_UNUSED ) {
129 : /* 5 huge pages for the key storage area */
130 0 : ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
131 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)`" );
132 0 : }
133 :
134 : static fd_keyswitch_t *
135 : find_keyswitch( fd_topo_t const * topo,
136 0 : char const * tile_name ) {
137 0 : ulong tile_idx = fd_topo_find_tile( topo, tile_name, 0UL );
138 0 : FD_TEST( tile_idx!=ULONG_MAX );
139 0 : FD_TEST( topo->tiles[ tile_idx ].id_keyswitch_obj_id!=ULONG_MAX );
140 :
141 0 : fd_keyswitch_t * keyswitch = fd_topo_obj_laddr( topo, topo->tiles[ tile_idx ].id_keyswitch_obj_id );
142 0 : FD_TEST( keyswitch );
143 0 : return keyswitch;
144 0 : }
145 :
146 : static void FD_FN_SENSITIVE
147 : poll_keyswitch( fd_topo_t * topo,
148 : ulong * state,
149 : ulong * halted_seq,
150 : uchar const * keypair,
151 : int * has_error,
152 : int require_tower,
153 : int require_vote_history,
154 0 : int force_lock ) {
155 0 : switch( *state ) {
156 0 : case FD_SET_IDENTITY_STATE_UNLOCKED: {
157 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
158 0 : if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &poh->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
159 0 : *state = FD_SET_IDENTITY_STATE_LOCKED;
160 0 : FD_LOG_INFO(( "Locking validator identity for key switch..." ));
161 0 : } else {
162 0 : if( FD_UNLIKELY( force_lock ) ) {
163 0 : *state = FD_SET_IDENTITY_STATE_LOCKED;
164 0 : FD_LOG_WARNING(( "Another process was changing keys, but `--force` supplied. Forcing lock on validator identity for key switch..." ));
165 0 : } else {
166 0 : FD_LOG_ERR(( "Cannot set-identity because Firedancer is already in the process of switching keys. If you are not currently "
167 0 : "changing the identity, it might be because an identity change was abandoned. To recover, run the `set-identity` "
168 0 : "command again with the `--force` argument." ));
169 0 : }
170 0 : }
171 0 : break;
172 0 : }
173 0 : case FD_SET_IDENTITY_STATE_LOCKED: {
174 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
175 0 : memcpy( poh->bytes, keypair, 64UL );
176 0 : poh->param = (ulong)!!require_tower | ((ulong)!!require_vote_history<<1);
177 0 : FD_COMPILER_MFENCE();
178 0 : poh->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
179 0 : FD_COMPILER_MFENCE();
180 0 : *state = FD_SET_IDENTITY_STATE_POH_HALT_REQUESTED;
181 0 : FD_LOG_INFO(( "Pausing leader pipeline for key switch..." ));
182 0 : break;
183 0 : }
184 0 : case FD_SET_IDENTITY_STATE_POH_HALT_REQUESTED: {
185 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
186 0 : if( FD_LIKELY( poh->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
187 0 : fd_memzero_explicit( poh->bytes, 64UL );
188 0 : FD_COMPILER_MFENCE();
189 0 : *halted_seq = poh->result;
190 0 : *state = FD_SET_IDENTITY_STATE_POH_HALTED;
191 0 : FD_LOG_INFO(( "Leader pipeline successfully paused..." ));
192 0 : } else if( FD_UNLIKELY( poh->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
193 0 : FD_SPIN_PAUSE();
194 0 : } else if( FD_LIKELY( poh->state==FD_KEYSWITCH_STATE_FAILED ) ) {
195 : /* Failed to switch identity in Agave, so abort the entire process. */
196 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCHED;
197 0 : *has_error = 1;
198 0 : } else {
199 0 : FD_LOG_ERR(( "Unexpected poh keyswitch state %lu", poh->state ));
200 0 : }
201 0 : break;
202 0 : }
203 0 : case FD_SET_IDENTITY_STATE_POH_HALTED: {
204 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
205 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
206 0 : if( FD_UNLIKELY( !strcmp( tile->name, "shred" ) ) ) {
207 0 : fd_keyswitch_t * shred = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
208 0 : FD_TEST( shred );
209 :
210 0 : shred->param = *halted_seq;
211 0 : memcpy( shred->bytes, keypair+32UL, 32UL );
212 0 : FD_COMPILER_MFENCE();
213 0 : shred->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
214 0 : FD_COMPILER_MFENCE();
215 0 : FD_LOG_INFO(( "Flushing in-flight unpublished shreds, must reach seq %lu...", *halted_seq ));
216 0 : } else if( FD_UNLIKELY( !strcmp( tile->name, "bundle" ) ) ) {
217 0 : fd_keyswitch_t * bundle = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
218 0 : FD_TEST( bundle );
219 :
220 0 : memcpy( bundle->bytes, keypair+32UL, 32UL );
221 0 : FD_COMPILER_MFENCE();
222 0 : bundle->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
223 0 : FD_COMPILER_MFENCE();
224 0 : }
225 0 : }
226 :
227 0 : *state = FD_SET_IDENTITY_STATE_SHRED_FLUSH_REQUESTED;
228 0 : break;
229 0 : }
230 0 : case FD_SET_IDENTITY_STATE_SHRED_FLUSH_REQUESTED: {
231 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
232 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
233 0 : if( FD_LIKELY( strcmp( tile->name, "shred" ) &&
234 0 : strcmp( tile->name, "bundle" ) ) ) continue;
235 :
236 0 : fd_keyswitch_t * keyswitch = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
237 0 : FD_TEST( keyswitch );
238 :
239 0 : if( FD_LIKELY( keyswitch->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
240 0 : continue;
241 0 : } else if( FD_UNLIKELY( keyswitch->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
242 : /* If any of the shred/bundle tiles is still pending, we need to wait. */
243 0 : FD_SPIN_PAUSE();
244 0 : return;
245 0 : } else {
246 0 : FD_LOG_ERR(( "Unexpected %s:%lu keyswitch state %lu", tile->name, tile->kind_id, keyswitch->state ));
247 0 : }
248 0 : }
249 :
250 0 : *state = FD_SET_IDENTITY_STATE_SHRED_FLUSHED;
251 0 : FD_LOG_INFO(( "All in-flight shreds published..." ));
252 0 : break;
253 0 : }
254 0 : case FD_SET_IDENTITY_STATE_SHRED_FLUSHED: {
255 0 : fd_keyswitch_t * sign = find_keyswitch( topo, "sign" );
256 0 : memcpy( sign->bytes, keypair, 64UL );
257 0 : FD_COMPILER_MFENCE();
258 0 : uchar * keypair_wr = fd_keyload_mprotect_wr( keypair, 0 );
259 0 : fd_memzero_explicit( keypair_wr, 32UL ); /* Private key no longer needed in this process */
260 0 : fd_keyload_mprotect_ro( keypair_wr, 0 );
261 0 : FD_COMPILER_MFENCE();
262 0 : sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
263 0 : FD_COMPILER_MFENCE();
264 :
265 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
266 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
267 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ||
268 0 : !strcmp( topo->tiles[ i ].name, "pohh" ) ||
269 0 : !strcmp( topo->tiles[ i ].name, "shred" ) ||
270 0 : !strcmp( topo->tiles[ i ].name, "bundle" ) ) ) continue;
271 :
272 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
273 0 : memcpy( tile_ks->bytes, keypair+32UL, 32UL );
274 0 : FD_COMPILER_MFENCE();
275 0 : tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
276 0 : FD_COMPILER_MFENCE();
277 0 : }
278 :
279 0 : FD_LOG_INFO(( "Requesting all tiles switch identity key..." ));
280 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED;
281 0 : break;
282 0 : }
283 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED: {
284 0 : ulong all_switched = 1UL;
285 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
286 0 : if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
287 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "pohh" ) ||
288 0 : !strcmp( topo->tiles[ i ].name, "shred" ) ||
289 0 : !strcmp( topo->tiles[ i ].name, "bundle" ) ) ) continue;
290 :
291 0 : fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
292 0 : if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
293 0 : all_switched = 0UL;
294 0 : break;
295 0 : } else if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
296 0 : if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ) ) {
297 0 : FD_COMPILER_MFENCE();
298 0 : fd_memzero_explicit( tile_ks->bytes, 64UL );
299 0 : FD_COMPILER_MFENCE();
300 0 : }
301 0 : continue;
302 0 : } else {
303 0 : FD_LOG_ERR(( "Unexpected %s keyswitch state %lu", topo->tiles[ i ].name, tile_ks->state ));
304 0 : }
305 0 : }
306 :
307 0 : if( FD_LIKELY( all_switched ) ) {
308 0 : FD_LOG_INFO(( "All tiles successfully switched identity key..." ));
309 0 : *state = FD_SET_IDENTITY_STATE_ALL_SWITCHED;
310 0 : } else {
311 0 : FD_SPIN_PAUSE();
312 0 : }
313 0 : break;
314 0 : }
315 0 : case FD_SET_IDENTITY_STATE_ALL_SWITCHED: {
316 0 : int bundle_exists = fd_topo_find_tile( topo, "bundle", 0UL )!=ULONG_MAX;
317 0 : if( FD_LIKELY( *has_error || !bundle_exists ) ) {
318 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
319 0 : FD_COMPILER_MFENCE();
320 0 : poh->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
321 0 : FD_COMPILER_MFENCE();
322 0 : FD_LOG_INFO(( "Requesting to unpause leader pipeline..." ));
323 0 : *state = FD_SET_IDENTITY_STATE_POH_UNHALT_REQUESTED;
324 0 : } else {
325 0 : fd_keyswitch_t * bundle = find_keyswitch( topo, "bundle" );
326 0 : FD_COMPILER_MFENCE();
327 0 : bundle->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
328 0 : FD_COMPILER_MFENCE();
329 0 : *state = FD_SET_IDENTITY_STATE_BUNDLE_UNHALT_REQUESTED;
330 0 : }
331 0 : break;
332 0 : }
333 0 : case FD_SET_IDENTITY_STATE_BUNDLE_UNHALT_REQUESTED: {
334 0 : fd_keyswitch_t * bundle = find_keyswitch( topo, "bundle" );
335 0 : if( FD_LIKELY( bundle->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
336 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
337 0 : FD_COMPILER_MFENCE();
338 0 : poh->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
339 0 : FD_COMPILER_MFENCE();
340 0 : FD_LOG_INFO(( "Requesting to unpause leader pipeline..." ));
341 0 : *state = FD_SET_IDENTITY_STATE_POH_UNHALT_REQUESTED;
342 0 : } else if( FD_UNLIKELY( bundle->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
343 0 : FD_SPIN_PAUSE();
344 0 : } else {
345 0 : FD_LOG_ERR(( "Unexpected bundle keyswitch state %lu", bundle->state ));
346 0 : }
347 0 : break;
348 0 : }
349 0 : case FD_SET_IDENTITY_STATE_POH_UNHALT_REQUESTED: {
350 0 : fd_keyswitch_t * poh = find_keyswitch( topo, "pohh" );
351 0 : if( FD_LIKELY( poh->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
352 0 : FD_LOG_INFO(( "Leader pipeline unpaused..." ));
353 0 : poh->state = FD_KEYSWITCH_STATE_UNLOCKED;
354 0 : *state = FD_SET_IDENTITY_STATE_UNLOCKED;
355 0 : } else if( FD_UNLIKELY( poh->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
356 0 : FD_SPIN_PAUSE();
357 0 : } else {
358 0 : FD_LOG_ERR(( "Unexpected poh keyswitch state %lu", poh->state ));
359 0 : }
360 0 : break;
361 0 : }
362 0 : }
363 0 : }
364 :
365 : void
366 : set_identityh_cmd_args( int * pargc,
367 : char *** pargv,
368 0 : args_t * args) {
369 0 : args->set_identity.require_tower = fd_env_strip_cmdline_contains( pargc, pargv, "--require-tower" );
370 0 : args->set_identity.require_vote_history = !fd_env_strip_cmdline_contains( pargc, pargv, "--do-not-require-vote-history" );
371 0 : args->set_identity.force = fd_env_strip_cmdline_contains( pargc, pargv, "--force" );
372 :
373 0 : if( FD_UNLIKELY( *pargc<1 ) ) goto err;
374 :
375 0 : char const * path = *pargv[0];
376 0 : (*pargc)--;
377 0 : (*pargv)++;
378 :
379 0 : if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
380 0 : uchar * keypair_wr = fd_keyload_alloc_protected_pages( 1UL, 2UL );
381 0 : FD_LOG_STDOUT(( "Reading identity keypair from stdin. Press Ctrl-D when done.\n" ));
382 0 : fd_keyload_read( STDIN_FILENO, "stdin", keypair_wr );
383 0 : args->set_identity.keypair = fd_keyload_mprotect_ro( keypair_wr, 0 );
384 0 : } else {
385 0 : args->set_identity.keypair = fd_keyload_load( path, 0 );
386 0 : }
387 :
388 0 : return;
389 :
390 0 : err:
391 0 : FD_LOG_ERR(( "Usage: %s set-identity <keypair> [--require-tower] [--do-not-require-vote-history] [--force]", FD_BINARY_NAME ));
392 0 : }
393 :
394 : static void FD_FN_SENSITIVE
395 : set_identity( args_t * args,
396 0 : config_t * config ) {
397 0 : uchar check_public_key[ 32 ];
398 0 : fd_sha512_t sha512[1];
399 0 : FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
400 0 : fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
401 0 : if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
402 0 : FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
403 0 : "Firedancer will not use the key pair to sign as it might leak the private key." ));
404 :
405 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
406 0 : fd_topo_tile_t * tile = &config->topo.tiles[ i ];
407 0 : if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue;
408 0 : fd_topo_obj_t * obj = &config->topo.objs[ tile->id_keyswitch_obj_id ];
409 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 );
410 0 : }
411 :
412 0 : int has_error = 0;
413 0 : ulong state = FD_SET_IDENTITY_STATE_UNLOCKED;
414 0 : ulong halted_seq = 0UL;
415 0 : for(;;) {
416 0 : poll_keyswitch( &config->topo, &state, &halted_seq, args->set_identity.keypair, &has_error, args->set_identity.require_tower, args->set_identity.require_vote_history, args->set_identity.force );
417 0 : if( FD_UNLIKELY( FD_SET_IDENTITY_STATE_UNLOCKED==state ) ) break;
418 0 : }
419 :
420 0 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
421 0 : fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
422 0 : identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
423 :
424 0 : if( FD_UNLIKELY( has_error ) ) FD_LOG_ERR(( "Failed to switch identity key to `%s`, check validator logs for details", identity_key_base58 ));
425 0 : else FD_LOG_NOTICE(( "Validator identity key switched to `%s`", identity_key_base58 ));
426 0 : }
427 :
428 : void
429 : set_identityh_cmd_fn( args_t * args,
430 0 : config_t * config ) {
431 0 : set_identity( args, config );
432 0 : }
433 :
434 : static void
435 0 : set_identityh_args_help( fd_action_help_t * help ) {
436 0 : fd_action_help_arg( help, "<keypair>", NULL, "Path to the new identity keypair, in the standard Solana keypair file\n"
437 0 : "format (the 64-byte JSON array). Pass `-` to read the same JSON\n"
438 0 : "array from stdin instead of from a file" );
439 0 : fd_action_help_arg( help, "--force", NULL, "Force the switch even though the validator reports a switch is already\n"
440 0 : "in progress. Only use this to recover after confirming no other\n"
441 0 : "set-identity is running, as forcing concurrently with a real switch\n"
442 0 : "can corrupt the switch and crash the validator" );
443 0 : fd_action_help_arg( help, "--require-tower", NULL, "Advanced: wait for the new identity's tower (its record of past votes)\n"
444 0 : "to be loaded before voting resumes, instead of starting to vote\n"
445 0 : "immediately. Use this when handing off to an identity that was\n"
446 0 : "recently voting elsewhere, to avoid voting on a fork it already voted\n"
447 0 : "against (which can get the validator slashed or stuck). Leave unset\n"
448 0 : "for ordinary identity changes, where the default is safe" );
449 : fd_action_help_arg( help, "--do-not-require-vote-history", NULL,
450 0 : "Advanced: allow the switch even if the new identity's vote history\n"
451 0 : "(its record of past Alpenglow votes) cannot be loaded. By default,\n"
452 0 : "if the vote account has prior Alpenglow votes, the switch fails\n"
453 0 : "unless the vote history file is present, to avoid voting against\n"
454 0 : "past votes (which can get the validator slashed). Has no effect\n"
455 0 : "before Alpenglow" );
456 0 : }
457 :
458 : action_t fd_action_set_identityh = {
459 : .name = "set-identity",
460 : .args = set_identityh_cmd_args,
461 : .fn = set_identityh_cmd_fn,
462 : .require_config = 1,
463 : .perm = NULL,
464 : .description = "Change the identity of a running validator",
465 : .detail = "Switches the gossip/voting/block-production identity key of an already\n"
466 : "running validator to the keypair you provide, without restarting it. On\n"
467 : "success it prints `Validator identity key switched to <pubkey>` and exits 0;\n"
468 : "on any error it exits non-zero and the identity is unchanged.\n"
469 : "\n"
470 : "This command does not start a validator; it attaches to one that is already\n"
471 : "running. It finds the running validator from the shared memory described by\n"
472 : "the configuration file, so you must point --config at the SAME config file the\n"
473 : "validator was started with, and run it from a binary built from the SAME git\n"
474 : "commit (compare this binary's `--version` against the running validator's). If\n"
475 : "the config or binary differ, the layout will not match and the command fails\n"
476 : "without changing anything.\n"
477 : "\n"
478 : "The change is live only: it is not written back to the config file, so the\n"
479 : "validator reverts to the configured [paths.identity_key] on its next restart.",
480 : .args_help = set_identityh_args_help,
481 : };
|