LCOV - code coverage report
Current view: top level - app/firedancer/commands - failover.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 242 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 28 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             : #include "../../../discof/admin/fd_adminctl.h"
       6             : #include "../../../disco/topo/fd_dns_resolve.h"
       7             : #include "../../../disco/keyguard/fd_keyload.h"
       8             : #include "../../../ballet/base58/fd_base58.h"
       9             : #include "../../../ballet/ed25519/fd_ed25519.h"
      10             : #include "../../../util/net/fd_ip4.h"
      11             : #include "../../platform/fd_cap_chk.h"
      12             : 
      13             : #include <errno.h>
      14             : #include <stdio.h>
      15             : #include <stdlib.h>
      16             : #include <unistd.h>
      17             : #include <sys/resource.h>
      18             : 
      19             : /* Strips key and its value. */
      20             : static char const *
      21             : strip_value( int *        pargc,
      22             :              char ***     pargv,
      23           0 :              char const * key ) {
      24           0 :   for( int i=0; i<*pargc; i++ ) {
      25           0 :     if( FD_UNLIKELY( !strcmp( (*pargv)[ i ], key ) && ( i+1==*pargc || !strncmp( (*pargv)[ i+1 ], "--", 2UL ) ) ) ) {
      26           0 :       FD_LOG_ERR(( "`%s` needs a value", key ));
      27           0 :     }
      28           0 :   }
      29           0 :   return fd_env_strip_cmdline_cstr( pargc, pargv, key, NULL, NULL );
      30           0 : }
      31             : 
      32             : /* Fetch and parse CLI args (Helper). */
      33             : static void
      34             : failover_args( int *    pargc,
      35             :                char *** pargv,
      36           0 :                args_t * args ) {
      37           0 :   if( FD_UNLIKELY( !*pargc || ( strcmp( (*pargv)[ 0 ], "promote" ) && strcmp( (*pargv)[ 0 ], "takeover" ) ) ) ) {
      38           0 :     FD_LOG_ERR(( "usage: firedancer failover <promote|takeover> [<keypair>] [OPTIONS]" ));
      39           0 :   }
      40           0 :   args->failover.takeover = !strcmp( (*pargv)[ 0 ], "takeover" );
      41           0 :   (*pargc)--;
      42           0 :   (*pargv)++;
      43           0 :   char const * name    = strip_value( pargc, pargv, "--name"    );
      44           0 :   char const * address = strip_value( pargc, pargv, "--address" );
      45           0 :   char const * port    = strip_value( pargc, pargv, "--port"    );
      46           0 :   if( FD_UNLIKELY( name ) ) fd_cstr_ncpy( args->failover.name, name, sizeof(args->failover.name) );
      47           0 :   args->failover.yes   = fd_env_strip_cmdline_contains( pargc, pargv, "--yes"   );
      48           0 :   args->failover.force = fd_env_strip_cmdline_contains( pargc, pargv, "--force" );
      49             : 
      50           0 :   if( FD_UNLIKELY( args->failover.force && !args->failover.takeover ) ) {
      51           0 :     FD_LOG_ERR(( "--force goes with `failover takeover`, `failover promote` hands the identity over cleanly" ));
      52           0 :   }
      53           0 :   if( FD_UNLIKELY( address && !fd_dns_resolve_address( address, &args->failover.addr ) ) ) {
      54           0 :     FD_LOG_ERR(( "could not resolve --address `%s` to an IPv4 address", address ));
      55           0 :   }
      56           0 :   if( FD_UNLIKELY( address && !args->failover.addr ) ) {
      57           0 :     FD_LOG_ERR(( "--address `%s` is 0.0.0.0, provide the address of the machine running the identity", address ));
      58           0 :   }
      59           0 :   if( port ) {
      60           0 :     uint value = 0U;
      61           0 :     for( char const * c=port; *c; c++ ) {
      62           0 :       uint digit = (uint)( *c-'0' );
      63           0 :       if( FD_UNLIKELY( digit>9U || value*10U+digit>65535U ) ) {
      64           0 :         FD_LOG_ERR(( "--port `%s` must be a port number from 1 to 65535", port ));
      65           0 :       }
      66           0 :       value = value*10U+digit;
      67           0 :     }
      68           0 :     if( FD_UNLIKELY( !value ) ) {
      69           0 :       FD_LOG_ERR(( "--port `%s` must be a port number from 1 to 65535", port ));
      70           0 :     }
      71           0 :     args->failover.port = (ushort)value;
      72           0 :   }
      73           0 :   if( *pargc && ( (*pargv)[ 0 ][ 0 ]!='-' || !strcmp( (*pargv)[ 0 ], "-" ) ) ) {
      74           0 :     fd_cstr_ncpy( args->failover.keypair_path, (*pargv)[ 0 ], sizeof(args->failover.keypair_path) );
      75           0 :     (*pargc)--;
      76           0 :     (*pargv)++;
      77           0 :   }
      78           0 : }
      79             : 
      80             : static void
      81           0 : failover_confirm( args_t const * args ) {
      82           0 :   if( args->failover.force ) {
      83           0 :     FD_LOG_STDOUT(( "This asks the machine running the identity to hand it over.  If that fails for any\n"
      84           0 :                     "reason, this machine takes the identity anyway.\n"
      85           0 :                     "WARNING: --force skips every check on the other machine, which may still run the\n"
      86           0 :                     "identity.  Use it only when that machine is not signing votes, or both machines\n"
      87           0 :                     "may double-vote.  Never take over on both machines at once.\n" ));
      88           0 :   } else if( args->failover.takeover ) {
      89           0 :     FD_LOG_STDOUT(( "This asks the machine running the identity to hand it over.  If that fails and\n"
      90           0 :                     "the other machine shows no signs of life, neither in gossip for 15 seconds nor on\n"
      91           0 :                     "the failover channel, this machine takes the identity anyway.\n"
      92           0 :                     "WARNING: a takeover skips the checks on the other machine.  A machine that is only\n"
      93           0 :                     "cut off from this one may still sign votes, and both machines may double-vote.\n" ));
      94           0 :   }
      95           0 :   if( FD_UNLIKELY( !args->failover.yes && isatty( STDIN_FILENO ) ) ) {
      96           0 :     if( !args->failover.takeover ) {
      97           0 :       FD_LOG_STDOUT(( "This asks the remote validator running this identity to hand it to this machine.  It keeps\n"
      98           0 :                       "the identity if this machine is more than 64 slots behind its finality anchor.\n"
      99           0 :                       "Otherwise, it gives up the identity before this validator adopts its history.\n"
     100           0 :                       "If replay stalls, the handoff can fail with neither machine voting.\n"
     101           0 :                       "Check that this machine is caught up.  Type yes to continue: " ));
     102           0 :     } else {
     103           0 :       FD_LOG_STDOUT(( "Type yes to continue: " ));
     104           0 :     }
     105           0 :     char line[ 16 ] = {0};
     106           0 :     if( FD_UNLIKELY( !fgets( line, sizeof(line), stdin ) ) ) FD_LOG_ERR(( "no confirmation given" ));
     107           0 :     if( FD_UNLIKELY( strcmp( line, "yes\n" ) ) ) FD_LOG_ERR(( "not confirmed, nothing was done" ));
     108           0 :   }
     109           0 : }
     110             : 
     111             : /* Prints a step of the command as the failover tile takes it. */
     112             : static void
     113           0 : failover_print_step( char const * step ) {
     114           0 :   FD_LOG_NOTICE(( "%s", step ));
     115           0 : }
     116             : 
     117             : static uchar const * FD_FN_SENSITIVE
     118           0 : failover_keypair_load( char const * path ) {
     119           0 :   if( FD_LIKELY( strcmp( path, "-" ) ) ) return fd_keyload_load( path, 0 );
     120           0 :   int fd = dup( STDIN_FILENO );
     121           0 :   if( FD_UNLIKELY( -1==fd ) ) FD_LOG_ERR(( "dup(stdin) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     122           0 :   uchar * keypair = fd_keyload_alloc_protected_pages( 1UL, 2UL );
     123           0 :   FD_LOG_STDOUT(( "Reading identity keypair from stdin.  Press Ctrl-D when done.\n" ));
     124           0 :   fd_keyload_read( fd, "stdin", keypair );
     125           0 :   return fd_keyload_mprotect_ro( keypair, 0 );
     126           0 : }
     127             : 
     128             : /* Sends req to the failover tile via adminctl and waits for the answer.
     129             :    Returns the adminctl result, along with the identity this machine will
     130             :    use afterwards in resp. */
     131             : static ulong FD_FN_SENSITIVE
     132             : failover_request( fd_adminctl_t *                    adminctl,
     133             :                   fd_adminctl_failover_req_t const * req,
     134             :                   uchar const *                      keypair,
     135           0 :                   fd_adminctl_failover_resp_t *      resp ) {
     136           0 :   void * payload     = NULL;
     137           0 :   ulong  payload_max = 0UL;
     138           0 :   ulong  slot_idx    = fd_adminctl_reserve( adminctl, &payload, &payload_max );
     139           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) FD_LOG_ERR(( "all admin command slots are busy" ));
     140           0 :   ulong payload_sz = keypair ? sizeof(fd_adminctl_failover_prepare_t) : sizeof(*req);
     141           0 :   if( FD_UNLIKELY( payload_sz>payload_max ) ) FD_LOG_ERR(( "adminctl failover payload too large" ));
     142           0 :   fd_memcpy( payload, req, sizeof(*req) );
     143           0 :   if( keypair ) fd_memcpy( ((fd_adminctl_failover_prepare_t *)payload)->keypair, keypair, 64UL );
     144             : 
     145           0 :   fd_adminctl_publish( adminctl, slot_idx, FD_ADMINCTL_CMD_FAILOVER_PROMOTE, payload_sz );
     146           0 :   ulong resp_sz = 0UL;
     147           0 :   ulong result  = fd_adminctl_wait_steps( adminctl, slot_idx, resp, sizeof(*resp), &resp_sz, failover_print_step );
     148           0 :   if( FD_UNLIKELY( resp_sz!=sizeof(*resp) || resp->version!=FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION ) ) fd_memset( resp, 0, sizeof(*resp) );
     149           0 :   return result;
     150           0 : }
     151             : 
     152             : /* Exit codes. */
     153             : 
     154           0 : #define FAILOVER_EXIT_OTHER   (1) /* any other failure, as FD_LOG_ERR exits */
     155           0 : #define FAILOVER_EXIT_REFUSED (2) /* refused, this command changed no identity */
     156             : #define FAILOVER_EXIT_UNKNOWN (3) /* the outcome is unknown */
     157           0 : #define FAILOVER_EXIT_NOBODY  (4) /* nobody runs the identity */
     158             : 
     159             : struct failover_result {
     160             :   ulong        result;
     161             :   char const * name;
     162             :   char const * meaning;
     163             :   int          exit_code;
     164             :   int          takeover;
     165             : };
     166             : typedef struct failover_result failover_result_t;
     167             : 
     168             : static failover_result_t const failover_results[] = {
     169             :   { FD_FAILOVER_CONTROL_RESULT_BUSY,              "busy",              "another failover command is running on the local validator",                                      FAILOVER_EXIT_REFUSED, 0 },
     170             :   { FD_FAILOVER_CONTROL_RESULT_IN_PROGRESS,       "in_progress",       "a handoff or identity switch is running on the local or the remote validator",                    FAILOVER_EXIT_REFUSED, 0 },
     171             :   { FD_FAILOVER_CONTROL_RESULT_NO_HOLDER_ADDRESS, "no_holder_address", "gossip did not show a validator running the identity",                                            FAILOVER_EXIT_REFUSED, 1 },
     172             :   { FD_FAILOVER_CONTROL_RESULT_NOT_HOLDER,        "not_holder",        "the validator at the address does not run the identity",                                          FAILOVER_EXIT_REFUSED, 1 },
     173             :   { FD_FAILOVER_CONTROL_RESULT_NO_PEER,           "no_peer",           "no validator running the identity answered at the address before the deadline",                   FAILOVER_EXIT_REFUSED, 1 },
     174             :   { FD_FAILOVER_CONTROL_RESULT_HANDED_OFF,        "handed_off",        "the local validator handed the identity off and the remote validator has not confirmed",          FAILOVER_EXIT_REFUSED, 1 },
     175             :   { FD_FAILOVER_CONTROL_RESULT_NO_FINAL_HISTORY,  "no_final_history",  "the remote validator has no vote history for its last vote yet",                                  FAILOVER_EXIT_REFUSED, 1 },
     176             :   { FD_FAILOVER_CONTROL_RESULT_REPLAY_BEHIND,     "replay_behind",     "the local validator's replay is too far behind the finality anchor of the vote history to adopt", FAILOVER_EXIT_REFUSED, 1 },
     177             :   { FD_FAILOVER_CONTROL_RESULT_HOLDS_OTHER,       "holds_other",       "the local validator runs a staked identity other than the one in the keypair",                    FAILOVER_EXIT_REFUSED, 0 },
     178             :   { FD_FAILOVER_CONTROL_RESULT_HOLDER_LIVE,       "holder_live",       "the remote validator may still be live",                                                          FAILOVER_EXIT_REFUSED, 0 },
     179             :   { FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH,      "keypair_mismatch",  "the public key in the keypair file does not match its private key",                               FAILOVER_EXIT_REFUSED, 0 },
     180             :   { FD_SET_IDENTITY_RESULT_AUTHORIZED_VOTER,      "authorized_voter",  "the identity is an authorized voter on the local validator",                                      FAILOVER_EXIT_REFUSED, 0 },
     181             :   { FD_FAILOVER_CONTROL_RESULT_TIMEOUT,           "timeout",           "the remote validator did not answer before the deadline",                                         FAILOVER_EXIT_UNKNOWN, 1 },
     182             :   { FD_FAILOVER_CONTROL_RESULT_PEER_RESTARTED,    "peer_restarted",    "the remote validator restarted before it answered",                                               FAILOVER_EXIT_UNKNOWN, 1 },
     183             :   { FD_FAILOVER_CONTROL_RESULT_HANDOFF_ABORTED,   "handoff_aborted",   "the remote validator aborted the handoff and may have given up the identity",                     FAILOVER_EXIT_UNKNOWN, 1 },
     184             :   { FD_FAILOVER_CONTROL_RESULT_PEER_HOLDS,        "peer_holds",        "another validator runs the identity",                                                             FAILOVER_EXIT_UNKNOWN, 1 },
     185             :   { FD_FAILOVER_CONTROL_RESULT_IDENTITY_CHANGED,  "identity_changed",  "set-identity changed the local validator's identity while the command ran",                       FAILOVER_EXIT_UNKNOWN, 0 },
     186             :   { FD_FAILOVER_CONTROL_RESULT_ADOPTION_FAILED,   "adoption_failed",   "the local validator did not adopt the vote history",                                              FAILOVER_EXIT_NOBODY,  1 },
     187             :   { FD_FAILOVER_CONTROL_RESULT_SWITCH_FAILED,     "switch_failed",     "the local validator's identity switch failed",                                                    FAILOVER_EXIT_NOBODY,  1 },
     188             : };
     189             : 
     190             : static failover_result_t const *
     191           0 : failover_result_find( ulong result ) {
     192           0 :   for( ulong i=0UL; i<sizeof(failover_results)/sizeof(failover_results[0]); i++ ) {
     193           0 :     if( failover_results[ i ].result==result ) return &failover_results[ i ];
     194           0 :   }
     195           0 :   return NULL;
     196           0 : }
     197             : 
     198           0 : #define FD_LOG_ERR_NOEXIT(a) do {                                       \
     199           0 :     long _fd_log_msg_now = fd_log_wallclock();                          \
     200           0 :     fd_log_private_1( 4, _fd_log_msg_now, __FILE__, __LINE__, __func__, \
     201           0 :                       fd_log_private_0 a );                             \
     202           0 :   } while(0)
     203             : 
     204             : /* The command as the operator typed it. */
     205             : static char const *
     206           0 : failover_cmd_name( args_t const * args ) {
     207           0 :   return !args->failover.takeover ? "failover promote" : args->failover.force ? "failover takeover --force" : "failover takeover";
     208           0 : }
     209             : 
     210             : /* The exit code for a failed command. */
     211             : static int
     212             : failover_exit_code( ulong result,
     213           0 :                     ulong handoff ) {
     214           0 :   int code = failover_result_find( result )->exit_code;
     215           0 :   if( handoff!=FD_ADMINCTL_RESULT_SUCCESS && handoff!=FD_FAILOVER_CONTROL_RESULT_HANDED_OFF && result!=FD_FAILOVER_CONTROL_RESULT_IDENTITY_CHANGED ) {
     216           0 :     code = failover_result_find( handoff )->exit_code;
     217           0 :   }
     218           0 :   return code;
     219           0 : }
     220             : 
     221             : static void __attribute__((noreturn))
     222             : fail_result( char const * cmd,
     223             :              ulong        result,
     224           0 :              ulong        handoff ) {
     225           0 :   switch( result ) {
     226           0 :     case FD_ADMINCTL_RESULT_UNKNOWN_COMMAND:
     227           0 :     case FD_ADMINCTL_RESULT_UNSUPPORTED:
     228           0 :     case FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH:
     229           0 :     case FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH:
     230           0 :       FD_LOG_ERR(( "this command is incompatible with the running validator" ));
     231           0 :     default:
     232           0 :       break;
     233           0 :   }
     234           0 :   failover_result_t const * row = failover_result_find( result );
     235           0 :   if( FD_UNLIKELY( !row ) ) {
     236           0 :     FD_LOG_ERR_NOEXIT(( "`%s` failed with result %#lx, check both validators with `firedancer get-identity`", cmd, result ));
     237           0 :     exit( FAILOVER_EXIT_OTHER );
     238           0 :   }
     239           0 :   int code = failover_exit_code( result, handoff );
     240           0 :   char const * state = code==FAILOVER_EXIT_REFUSED ? "this command changed no identity" :
     241           0 :                        code==FAILOVER_EXIT_NOBODY  ? "nobody runs the identity" :
     242           0 :                                                      "the outcome is unknown, check both validators with `firedancer get-identity`";
     243           0 :   FD_LOG_ERR_NOEXIT(( "`%s` failed (%s), %s, %s", cmd, row->name, row->meaning, state ));
     244           0 :   exit( code );
     245           0 : }
     246             : 
     247             : /* Run sanity checks before invoking failover. */
     248             : static void FD_FN_SENSITIVE
     249             : failover_preflight( fd_adminctl_t * adminctl,
     250             :                     args_t const *  args,
     251           0 :                     uchar const *   keypair ) {
     252           0 :   fd_adminctl_failover_req_t req = { .version=FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION, .cmd=FD_ADMINCTL_FAILOVER_CMD_PREFLIGHT };
     253           0 :   if( FD_UNLIKELY( args->failover.takeover ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER;
     254           0 :   if( FD_UNLIKELY( args->failover.force    ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_FORCE;
     255           0 :   fd_sha512_t sha512[ 1 ];
     256           0 :   FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
     257           0 :   fd_ed25519_public_from_private( req.target, keypair, sha512 );
     258           0 :   fd_adminctl_failover_resp_t resp;
     259           0 :   ulong result = failover_request( adminctl, &req, NULL, &resp );
     260           0 :   if( FD_UNLIKELY( result!=FD_ADMINCTL_RESULT_SUCCESS ) ) fail_result( failover_cmd_name( args ), result, FD_ADMINCTL_RESULT_SUCCESS );
     261           0 : }
     262             : 
     263             : /* Asks the failover tile for the handoff and waits for its outcome. */
     264             : static void
     265             : failover_cmd_fn( args_t *   args,
     266           0 :                  config_t * config ) {
     267           0 :   fd_adminctl_t * adminctl = adminctl_client_attach( config, args->failover.name );
     268           0 :   char const *    cmd      = failover_cmd_name( args );
     269           0 :   uchar const *   keypair  = args->failover.keypair_path[ 0 ] ? failover_keypair_load( args->failover.keypair_path ) : NULL;
     270           0 :   if( keypair ) failover_preflight( adminctl, args, keypair );
     271           0 :   failover_confirm( args );
     272             : 
     273           0 :   fd_adminctl_failover_req_t req;
     274           0 :   fd_memset( &req, 0, sizeof(req) );
     275           0 :   req.version = FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION;
     276           0 :   req.cmd     = FD_ADMINCTL_FAILOVER_CMD_HANDOFF;
     277           0 :   if( FD_UNLIKELY( args->failover.yes      ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_YES;
     278           0 :   if( FD_UNLIKELY( args->failover.takeover ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER;
     279           0 :   if( FD_UNLIKELY( args->failover.force    ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_FORCE;
     280           0 :   req.addr = args->failover.addr;
     281           0 :   req.port = args->failover.port;
     282             : 
     283           0 :   char const * bold   = fd_log_style_bold();
     284           0 :   char const * normal = fd_log_style_normal();
     285           0 :   if( args->failover.addr ) {
     286           0 :     FD_LOG_NOTICE(( "requesting handoff of the identity from peer `" FD_IP4_ADDR_FMT ":%hu`", FD_IP4_ADDR_FMT_ARGS( args->failover.addr ),
     287           0 :                     (ushort)( args->failover.port ? args->failover.port : 8010 ) ));
     288           0 :   } else {
     289           0 :     FD_LOG_NOTICE(( "requesting handoff of the identity from the peer running it in gossip" ));
     290           0 :   }
     291             : 
     292           0 :   fd_adminctl_failover_resp_t resp;
     293           0 :   ulong result  = failover_request( adminctl, &req, keypair, &resp );
     294           0 :   ulong handoff = FD_ADMINCTL_RESULT_SUCCESS;
     295             : 
     296           0 :   failover_result_t const * failed = failover_result_find( result );
     297           0 :   if( FD_UNLIKELY( failed && failed->takeover && args->failover.takeover ) ) {
     298           0 :     FD_LOG_WARNING(( "the handoff failed (%s), %s", failed->name, failed->meaning ));
     299           0 :     handoff = result;
     300           0 :     if( args->failover.force ) FD_LOG_NOTICE(( "taking the identity without the other machine, --force skips all checks" ));
     301           0 :     else                       FD_LOG_NOTICE(( "taking the identity if the other machine shows no signs of life" ));
     302             : 
     303           0 :     req.cmd   = FD_ADMINCTL_FAILOVER_CMD_PROMOTE;
     304           0 :     req.flags = req.flags & (FD_ADMINCTL_FAILOVER_FLAG_YES|FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER|FD_ADMINCTL_FAILOVER_FLAG_FORCE);
     305           0 :     req.addr  = 0U;
     306           0 :     req.port  = (ushort)0;
     307           0 :     if( keypair ) fd_memcpy( req.target, keypair+32UL, 32UL );
     308           0 :     result    = failover_request( adminctl, &req, NULL, &resp );
     309           0 :   }
     310           0 :   if( keypair ) fd_keyload_unload( keypair, 0 );
     311             : 
     312           0 :   int already = result==FD_FAILOVER_CONTROL_RESULT_ALREADY_HOLDS;
     313           0 :   if( FD_UNLIKELY( result!=FD_ADMINCTL_RESULT_SUCCESS && !already ) ) fail_result( cmd, result, handoff );
     314           0 :   if( FD_UNLIKELY( !resp.version ) ) {
     315           0 :     FD_LOG_ERR_NOEXIT(( "`%s` returned an incompatible response", cmd ));
     316           0 :     exit( FAILOVER_EXIT_OTHER );
     317           0 :   }
     318           0 :   FD_BASE58_ENCODE_32_BYTES( resp.identity, identity_b58 );
     319           0 :   if( FD_UNLIKELY( already ) ) {
     320           0 :     FD_LOG_NOTICE(( "the local validator already runs the identity %s%s%s", bold, identity_b58, normal ));
     321           0 :     return;
     322           0 :   }
     323           0 :   if( resp.slot==ULONG_MAX ) FD_LOG_NOTICE(( "promotion success (identity now %s%s%s, no earlier vote)", bold, identity_b58, normal ));
     324           0 :   else                       FD_LOG_NOTICE(( "promotion success (identity now %s%s%s at slot %lu)", bold, identity_b58, normal, resp.slot ));
     325           0 : }
     326             : 
     327             : static void
     328           0 : failover_args_help( fd_action_help_t * help ) {
     329           0 :   fd_action_help_arg( help, "promote",     NULL,        "Ask the machine running the identity to hand it over to this validator and\n"
     330           0 :                                                         "wait for the result.  For planned switches, the other machine must be up." );
     331           0 :   fd_action_help_arg( help, "takeover",    NULL,        "Like promote, but if the handoff fails and the machine running the identity\n"
     332           0 :                                                         "shows no signs of life, take the identity without it, from an empty vote\n"
     333           0 :                                                         "history.  Use when that machine is down or unreachable." );
     334           0 :   fd_action_help_arg( help, "[<keypair>]", NULL,        "Path to the keypair of the identity to take, in the standard Solana keypair\n"
     335           0 :                                                         "file format (the 64-byte JSON array), or `-` to read it from stdin.  Defaults\n"
     336           0 :                                                         "to [paths.identity_key] or the last keypair given to `failover` or\n"
     337           0 :                                                         "`set-identity`." );
     338           0 :   fd_action_help_arg( help, "--name",      "<name>",    "Name of the validator instance to attach to, if more than one is\n"
     339           0 :                                                         "running on this host" );
     340           0 :   fd_action_help_arg( help, "--yes",       NULL,        "Bypass the interactive confirmation prompt." );
     341           0 :   fd_action_help_arg( help, "--force",     NULL,        "takeover only.  Take the identity even if the machine running it still shows\n"
     342           0 :                                                         "signs of life, skipping every check.  Only use when that machine cannot sign,\n"
     343           0 :                                                         "or both machines may double-vote." );
     344           0 :   fd_action_help_arg( help, "--address",   "<address>", "The target machine's IPv4 address or host name" );
     345           0 :   fd_action_help_arg( help, "--port",      "<port>",    "The target machine's failover port, 8010 by default" );
     346           0 : }
     347             : 
     348             : static void
     349             : failover_promote_perm( args_t *         args,
     350             :                        fd_cap_chk_t *   chk,
     351           0 :                        config_t const * config FD_PARAM_UNUSED ) {
     352           0 :   if( args->failover.keypair_path[ 0 ] ) {
     353           0 :     fd_cap_chk_raise_rlimit( chk, failover_cmd_name( args ), RLIMIT_MEMLOCK, 5UL*FD_SHMEM_NORMAL_PAGE_SZ,
     354           0 :                              "lock the target keypair in memory" );
     355           0 :   }
     356           0 : }
     357             : 
     358             : action_t fd_action_failover = {
     359             :   .name           = "failover",
     360             :   .args           = failover_args,
     361             :   .fn             = failover_cmd_fn,
     362             :   .require_config = 0,
     363             :   .no_topo        = 1,
     364             :   .perm           = failover_promote_perm,
     365             :   .description    = "Take the identity that another validator runs",
     366             :   .detail         = "Hands off an identity between validators.  Each boots under its own\n"
     367             :                     "[paths.identity_key].  On the machine that should take over, pass the keypair\n"
     368             :                     "of the target identity.\n"
     369             :                     "A validator with [failover.boot_to_standby] boots as a standby under a junk key\n"
     370             :                     "generated in memory and loads the keypair in its [paths.identity_key] as the\n"
     371             :                     "identity to take over, so `failover` there reads the keypair from the\n"
     372             :                     "configuration instead of the command line.\n"
     373             :                     "\n"
     374             :                     "`failover promote` asks the machine running the identity to hand it over.\n"
     375             :                     "`failover takeover` does the same, and if that fails and the other machine shows\n"
     376             :                     "no signs of life, takes the identity without handover.  With --force, it takes\n"
     377             :                     "the identity even from a machine that may be live.\n"
     378             :                     "This command asks for confirmation unless --yes is given.\n"
     379             :                     "It waits for the outcome and exits 0 once this machine runs the identity,\n"
     380             :                     "and non-zero with the reason when it does not.  For checking status,\n"
     381             :                     "`get-identity` prints the key a machine runs.\n"
     382             :                     "\n"
     383             :                     "This command does not start a validator. It attaches to one that is already\n"
     384             :                     "running.  If multiple validators are running, pass --name to select one.\n",
     385             :   .usage          = "failover <promote|takeover> [<keypair>] [--name <name>] [--yes] [--force] [--address <address>] [--port <port>]",
     386             :   .args_help      = failover_args_help,
     387             : };

Generated by: LCOV version 1.14