LCOV - code coverage report
Current view: top level - app/firedancer/commands - set_identity.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 109 0.0 %
Date: 2026-10-22 18:10:43 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             : #include "../../../choreo/votor/ag_vote_history_serde.h"
      12             : 
      13             : #include <errno.h>
      14             : #include <fcntl.h>
      15             : #include <strings.h>
      16             : #include <sys/mman.h>
      17             : #include <sys/stat.h>
      18             : #include <unistd.h>
      19             : #include <sys/resource.h>
      20             : 
      21             : void
      22             : set_identity_cmd_perm( args_t *         args   FD_PARAM_UNUSED,
      23             :                        fd_cap_chk_t *   chk,
      24           0 :                        config_t const * config FD_PARAM_UNUSED ) {
      25             :   /* 5 huge pages for the key storage area */
      26           0 :   ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
      27           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)`" );
      28           0 : }
      29             : 
      30             : void
      31             : set_identity_cmd_args( int *    pargc,
      32             :                        char *** pargv,
      33           0 :                        args_t * args) {
      34             : 
      35           0 :   char const * name = fd_env_strip_cmdline_cstr( pargc, pargv, "--name", NULL, NULL );
      36           0 :   if( FD_UNLIKELY( name ) ) fd_cstr_ncpy( args->set_identity.name, name, sizeof(args->set_identity.name) );
      37             : 
      38           0 :   if( FD_UNLIKELY( *pargc && !strcmp( (*pargv)[ *pargc-1 ], "--vote-history-file" ) ) ) goto err;
      39           0 :   char const * vote_history_file = fd_env_strip_cmdline_cstr( pargc, pargv, "--vote-history-file", NULL, NULL );
      40             : 
      41           0 :   if( FD_UNLIKELY( *pargc<1 ) ) goto err;
      42             : 
      43           0 :   char const * path = *pargv[0];
      44           0 :   (*pargc)--;
      45           0 :   (*pargv)++;
      46             : 
      47           0 :   if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
      48           0 :     uchar * keypair_wr = fd_keyload_alloc_protected_pages( 1UL, 2UL );
      49           0 :     FD_LOG_STDOUT(( "Reading identity keypair from stdin.  Press Ctrl-D when done.\n" ));
      50           0 :     fd_keyload_read( STDIN_FILENO, "stdin", keypair_wr );
      51           0 :     args->set_identity.keypair = fd_keyload_mprotect_ro( keypair_wr, 0 );
      52           0 :   } else {
      53           0 :     args->set_identity.keypair = fd_keyload_load( path, 0 );
      54           0 :   }
      55             : 
      56             :   /* Scan a vote history file for the slot to wait to vote past, which
      57             :      is all the validator needs. */
      58             : 
      59           0 :   args->set_identity.vote_history_slot = 0UL;
      60           0 :   if( vote_history_file ) {
      61           0 :     int fd = open( vote_history_file, O_RDONLY );
      62           0 :     if( FD_UNLIKELY( -1==fd ) ) FD_LOG_ERR(( "open(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) ));
      63           0 :     struct stat st;
      64           0 :     if( FD_UNLIKELY( -1==fstat( fd, &st ) ) ) FD_LOG_ERR(( "fstat(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) ));
      65           0 :     ulong sz = (ulong)st.st_size;
      66           0 :     if( FD_UNLIKELY( !sz ) ) {
      67           0 :       FD_LOG_ERR(( "vote history file %s is empty.  If you wish to change a running validator's identity without "
      68           0 :                    "a vote history file, you can omit the --vote-history-file argument.", vote_history_file ));
      69           0 :     }
      70           0 :     uchar const * file = mmap( NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0 );
      71           0 :     if( FD_UNLIKELY( file==MAP_FAILED ) ) FD_LOG_ERR(( "mmap(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) ));
      72           0 :     if( FD_UNLIKELY( -1==close( fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      73             : 
      74           0 :     int err = ag_vote_history_de( file, sz, args->set_identity.keypair+32UL, &args->set_identity.vote_history_slot );
      75           0 :     if( FD_UNLIKELY( err ) ) {
      76           0 :       FD_LOG_ERR(( "vote history file %s is invalid (%i) or does not belong to the new identity.  If you believe the "
      77           0 :                    "vote history file is valid, please contact the Firedancer team for additional assistance.", vote_history_file, err ));
      78           0 :     }
      79           0 :     if( FD_UNLIKELY( munmap( (void *)file, sz ) ) ) FD_LOG_ERR(( "munmap(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) ));
      80           0 :   }
      81           0 :   return;
      82             : 
      83           0 : err:
      84           0 :   FD_LOG_ERR(( "Usage: %s set-identity <keypair> [--name <name>] [--vote-history-file <path>]", FD_BINARY_NAME ));
      85           0 : }
      86             : 
      87             : static void FD_FN_SENSITIVE
      88             : set_identity( args_t *   args,
      89           0 :               config_t * config ) {
      90           0 :   uchar       check_public_key[ 32 ];
      91           0 :   fd_sha512_t sha512[ 1 ];
      92           0 :   FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
      93           0 :   fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
      94           0 :   if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
      95           0 :     FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
      96           0 :                  "Firedancer will not use the key pair to sign as it might leak the private key." ));
      97             : 
      98           0 :   char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
      99           0 :   fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
     100           0 :   identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
     101             : 
     102           0 :   fd_adminctl_t * adminctl = adminctl_client_attach( config, args->set_identity.name );
     103             : 
     104           0 :   void * payload     = NULL;
     105           0 :   ulong  payload_max = 0UL;
     106           0 :   ulong  slot_idx    = fd_adminctl_reserve( adminctl, &payload, &payload_max );
     107           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) {
     108           0 :     FD_LOG_ERR(( "Failed to process `set-identity` command as there are other pending "
     109           0 :                  "commands that are being processed.  Please wait for other commands to complete "
     110           0 :                  "or forcefully terminate the other processes and retry the command." ));
     111           0 :   }
     112           0 :   if( FD_UNLIKELY( sizeof(fd_adminctl_set_identity_t)>payload_max ) ) FD_LOG_ERR(( "adminctl set-identity payload too large" ));
     113             : 
     114           0 :   fd_adminctl_set_identity_t * req = (fd_adminctl_set_identity_t *)payload;
     115           0 :   req->version           = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION;
     116           0 :   req->vote_history_slot = args->set_identity.vote_history_slot;
     117           0 :   memcpy( req->keypair, args->set_identity.keypair, 64UL );
     118             : 
     119           0 :   uchar * keypair_wr = fd_keyload_mprotect_wr( args->set_identity.keypair, 0 );
     120           0 :   fd_memzero_explicit( keypair_wr, 64UL );
     121           0 :   fd_keyload_mprotect_ro( keypair_wr, 0 );
     122             : 
     123           0 :   fd_adminctl_publish( adminctl, slot_idx, FD_ADMINCTL_CMD_SET_IDENTITY, sizeof(fd_adminctl_set_identity_t) );
     124             : 
     125           0 :   ulong result = fd_adminctl_wait( adminctl, slot_idx );
     126           0 :   switch( result ) {
     127           0 :     case FD_ADMINCTL_RESULT_SUCCESS:
     128           0 :       FD_LOG_NOTICE(( "validator identity key switched to %s%s%s", fd_log_style_bold(), identity_key_base58, fd_log_style_normal() ));
     129           0 :       break;
     130           0 :     case FD_SET_IDENTITY_RESULT_SAME_IDENTITY:
     131           0 :       FD_LOG_ERR(( "Failed to set identity: the validator is already running as %s.", identity_key_base58 ));
     132           0 :     case FD_SET_IDENTITY_RESULT_INVALID_VOTE_HISTORY:
     133           0 :       FD_LOG_ERR(( "Failed to set identity: the vote history file is invalid or does not belong to the new identity. "
     134           0 :                    "If you believe the vote history file is valid, please contact the Firedancer team for additional assistance." ));
     135           0 :     case FD_ADMINCTL_RESULT_UNKNOWN_COMMAND:
     136           0 :     case FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH:
     137           0 :     case FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH:
     138           0 :     case FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH:
     139           0 :       FD_LOG_ERR(( "Failed to set identity: the command was not able to successfully communicate "
     140           0 :                    "with the running Firedancer process. It is possible that you are running the "
     141           0 :                    "command from an older or newer version of Firedancer that is no longer compatible." ));
     142           0 :     default:
     143           0 :       FD_LOG_ERR(( "Unexpected set-identity result %lu.  This can be a result of a version mismatch "
     144           0 :                    "between the command and the running Firedancer process. Please report this to the "
     145           0 :                    "Firedancer team for investigation.", result ));
     146           0 :   }
     147           0 : }
     148             : 
     149             : void
     150             : set_identity_cmd_fn( args_t *   args,
     151           0 :                      config_t * config ) {
     152           0 :   set_identity( args, config );
     153           0 : }
     154             : 
     155             : static void
     156           0 : set_identity_args_help( fd_action_help_t * help ) {
     157           0 :   fd_action_help_arg( help, "<keypair>",           NULL,     "Path to the new identity keypair, in the standard Solana keypair file\n"
     158           0 :                                                              "format (the 64-byte JSON array).  Pass `-` to read the same JSON\n"
     159           0 :                                                              "array from stdin instead of from a file" );
     160           0 :   fd_action_help_arg( help, "--name",              "<name>", "Name of the validator instance to attach to, if more than one is\n"
     161           0 :                                                              "running on this host" );
     162           0 :   fd_action_help_arg( help, "--vote-history-file", "<path>", "Path to the vote history file for the new identity" );
     163           0 : }
     164             : 
     165             : action_t fd_action_set_identity = {
     166             :   .name           = "set-identity",
     167             :   .args           = set_identity_cmd_args,
     168             :   .fn             = set_identity_cmd_fn,
     169             :   .require_config = 0,
     170             :   .perm           = set_identity_cmd_perm,
     171             :   .description    = "Change the identity of a running validator",
     172             :   .detail         = "Switches the gossip/voting/block-production identity key of an already\n"
     173             :                     "running validator to the keypair you provide, without restarting it.  The\n"
     174             :                     "switch is atomic: the validator briefly pauses replay and block production so\n"
     175             :                     "it never signs with a mix of the old and new keys, then resumes under the new\n"
     176             :                     "identity.  On success it prints `Validator identity key switched to <pubkey>`\n"
     177             :                     "and exits 0; a rejected request exits non-zero.\n"
     178             :                     "\n"
     179             :                     "With --vote-history-file, the validator does not vote until the leader window\n"
     180             :                     "after the highest slot the file voted in.\n"
     181             :                     "\n"
     182             :                     "This command does not start a validator; it attaches to one that is already\n"
     183             :                     "running.  With no arguments it discovers the running validator automatically.\n"
     184             :                     "If multiple validators are running, pass --name to select one.  If --config is\n"
     185             :                     "given, the validator is instead located from the configuration file; only the\n"
     186             :                     "name and [hugetlbfs.mount_path] values are used, and they must match the\n"
     187             :                     "running validator.\n"
     188             :                     "\n"
     189             :                     "The change is live only: it is not written back to the config file, so the\n"
     190             :                     "validator reverts to the configured [paths.identity_key] on its next restart.\n"
     191             :                     "To make the new identity permanent, also update that path in the config.\n",
     192             :   .usage          = "set-identity <keypair> [--name <name>] [--vote-history-file <path>]",
     193             :   .args_help      = set_identity_args_help,
     194             : };

Generated by: LCOV version 1.14