LCOV - code coverage report
Current view: top level - app/fdctl/commands - set_identityh.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 290 0.0 %
Date: 2026-08-13 04:56:22 Functions: 0 7 0.0 %

          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             : };

Generated by: LCOV version 1.14