LCOV - code coverage report
Current view: top level - app/firedancer/commands - set_identity.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 79 0.0 %
Date: 2026-08-14 04:54:57 Functions: 0 5 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "adminctl_client.h"
       3             : #include "../../shared/fd_config.h"
       4             : #include "../../shared/fd_action.h"
       5             : 
       6             : #include <unistd.h>
       7             : #include "../../platform/fd_cap_chk.h"
       8             : #include "../../../disco/keyguard/fd_keyload.h"
       9             : #include "../../../ballet/base58/fd_base58.h"
      10             : #include "../../../ballet/ed25519/fd_ed25519.h"
      11             : 
      12             : #include <strings.h>
      13             : #include <unistd.h>
      14             : #include <sys/resource.h>
      15             : 
      16             : void
      17             : set_identity_cmd_perm( args_t *         args   FD_PARAM_UNUSED,
      18             :                        fd_cap_chk_t *   chk,
      19           0 :                        config_t const * config FD_PARAM_UNUSED ) {
      20             :   /* 5 huge pages for the key storage area */
      21           0 :   ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
      22           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)`" );
      23           0 : }
      24             : 
      25             : void
      26             : set_identity_cmd_args( int *    pargc,
      27             :                        char *** pargv,
      28           0 :                        args_t * args) {
      29             : 
      30           0 :   char const * name = fd_env_strip_cmdline_cstr( pargc, pargv, "--name", NULL, NULL );
      31           0 :   if( FD_UNLIKELY( name ) ) fd_cstr_ncpy( args->set_identity.name, name, sizeof(args->set_identity.name) );
      32             : 
      33           0 :   if( FD_UNLIKELY( *pargc<1 ) ) goto err;
      34             : 
      35           0 :   char const * path = *pargv[0];
      36           0 :   (*pargc)--;
      37           0 :   (*pargv)++;
      38             : 
      39           0 :   if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
      40           0 :     uchar * keypair_wr = fd_keyload_alloc_protected_pages( 1UL, 2UL );
      41           0 :     FD_LOG_STDOUT(( "Reading identity keypair from stdin.  Press Ctrl-D when done.\n" ));
      42           0 :     fd_keyload_read( STDIN_FILENO, "stdin", keypair_wr );
      43           0 :     args->set_identity.keypair = fd_keyload_mprotect_ro( keypair_wr, 0 );
      44           0 :   } else {
      45           0 :     args->set_identity.keypair = fd_keyload_load( path, 0 );
      46           0 :   }
      47             : 
      48           0 :   return;
      49             : 
      50           0 : err:
      51           0 :   FD_LOG_ERR(( "Usage: %s set-identity <keypair>", FD_BINARY_NAME ));
      52           0 : }
      53             : 
      54             : static void FD_FN_SENSITIVE
      55             : set_identity( args_t *   args,
      56           0 :               config_t * config ) {
      57           0 :   uchar       check_public_key[ 32 ];
      58           0 :   fd_sha512_t sha512[ 1 ];
      59           0 :   FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
      60           0 :   fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
      61           0 :   if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
      62           0 :     FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
      63           0 :                  "Firedancer will not use the key pair to sign as it might leak the private key." ));
      64             : 
      65           0 :   char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
      66           0 :   fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
      67           0 :   identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
      68             : 
      69           0 :   fd_adminctl_t * adminctl = adminctl_client_attach( config, args->set_identity.name );
      70             : 
      71           0 :   void * payload     = NULL;
      72           0 :   ulong  payload_max = 0UL;
      73           0 :   ulong  slot_idx    = fd_adminctl_reserve( adminctl, &payload, &payload_max );
      74           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) {
      75           0 :     FD_LOG_ERR(( "Failed to process `set-identity` command as there are other pending "
      76           0 :                  "commands that are being processed.  Please wait for other commands to complete "
      77           0 :                  "or forcefully terminate the other processes and retry the command." ));
      78           0 :   }
      79           0 :   if( FD_UNLIKELY( sizeof(fd_adminctl_set_identity_t)>payload_max ) ) FD_LOG_ERR(( "adminctl set-identity payload too large" ));
      80             : 
      81           0 :   fd_adminctl_set_identity_t * req = (fd_adminctl_set_identity_t *)payload;
      82           0 :   req->version = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION;
      83           0 :   memcpy( req->keypair, args->set_identity.keypair, 64UL );
      84             : 
      85           0 :   uchar * keypair_wr = fd_keyload_mprotect_wr( args->set_identity.keypair, 0 );
      86           0 :   fd_memzero_explicit( keypair_wr, 64UL );
      87           0 :   fd_keyload_mprotect_ro( keypair_wr, 0 );
      88             : 
      89           0 :   fd_adminctl_publish( adminctl, slot_idx, FD_ADMINCTL_CMD_SET_IDENTITY, sizeof(fd_adminctl_set_identity_t) );
      90             : 
      91           0 :   ulong result = fd_adminctl_wait( adminctl, slot_idx );
      92           0 :   switch( result ) {
      93           0 :     case FD_ADMINCTL_RESULT_SUCCESS:
      94           0 :       FD_LOG_NOTICE(( "validator identity key switched to %s%s%s", fd_log_style_bold(), identity_key_base58, fd_log_style_normal() ));
      95           0 :       break;
      96           0 :     case FD_ADMINCTL_RESULT_UNKNOWN_COMMAND:
      97           0 :     case FD_SET_IDENTITY_RESULT_PAYLOAD_TOO_SMALL:
      98           0 :     case FD_SET_IDENTITY_RESULT_UNSUPPORTED_PAYLOAD_VERSION:
      99           0 :     case FD_SET_IDENTITY_RESULT_UNEXPECTED_PAYLOAD_SIZE:
     100           0 :     case FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH:
     101           0 :       FD_LOG_ERR(( "Failed to set identity: the command was not able to successfully communicate "
     102           0 :                    "with the running Firedancer process. It is possible that you are running the "
     103           0 :                    "command from an older or newer version of Firedancer that is no longer compatible." ));
     104           0 :     default:
     105           0 :       FD_LOG_ERR(( "Unexpected set-identity result %lu.  This can be a result of a version mismatch "
     106           0 :                    "between the command and the running Firedancer process. Please report this to the "
     107           0 :                    "Firedancer team for investigation.", result ));
     108           0 :   }
     109           0 : }
     110             : 
     111             : void
     112             : set_identity_cmd_fn( args_t *   args,
     113           0 :                      config_t * config ) {
     114           0 :   set_identity( args, config );
     115           0 : }
     116             : 
     117             : static void
     118           0 : set_identity_args_help( fd_action_help_t * help ) {
     119             :   fd_action_help_arg( help, "<keypair>", NULL,   "Path to the new identity keypair, in the standard Solana keypair file\n"
     120           0 :                                                  "format (the 64-byte JSON array).  Pass `-` to read the same JSON\n"
     121           0 :                                                  "array from stdin instead of from a file" );
     122           0 :   fd_action_help_arg( help, "--name", "<name>",  "Name of the validator instance to attach to, if more than one is\n"
     123           0 :                                                  "running on this host" );
     124           0 : }
     125             : 
     126             : action_t fd_action_set_identity = {
     127             :   .name           = "set-identity",
     128             :   .args           = set_identity_cmd_args,
     129             :   .fn             = set_identity_cmd_fn,
     130             :   .require_config = 0,
     131             :   .perm           = set_identity_cmd_perm,
     132             :   .description    = "Change the identity of a running validator",
     133             :   .detail         = "Switches the gossip/voting/block-production identity key of an already\n"
     134             :                     "running validator to the keypair you provide, without restarting it.  The\n"
     135             :                     "switch is atomic: the validator briefly pauses block production so it never\n"
     136             :                     "signs with a mix of the old and new keys, then resumes under the new\n"
     137             :                     "identity.  On success it prints `Validator identity key switched to <pubkey>`\n"
     138             :                     "and exits 0; on any error it exits non-zero and the identity is unchanged.\n"
     139             :                     "\n"
     140             :                     "This command does not start a validator; it attaches to one that is already\n"
     141             :                     "running.  With no arguments it discovers the running validator automatically.\n"
     142             :                     "If multiple validators are running, pass --name to select one.  If --config is\n"
     143             :                     "given, the validator is instead located from the configuration file; only the\n"
     144             :                     "name and [hugetlbfs.mount_path] values are used, and they must match the\n"
     145             :                     "running validator.\n"
     146             :                     "\n"
     147             :                     "The change is live only: it is not written back to the config file, so the\n"
     148             :                     "validator reverts to the configured [paths.identity_key] on its next restart.\n"
     149             :                     "To make the new identity permanent, also update that path in the config.\n",
     150             :   .usage          = "set-identity <keypair> [--name <name>]",
     151             :   .args_help      = set_identity_args_help,
     152             : };

Generated by: LCOV version 1.14