LCOV - code coverage report
Current view: top level - app/firedancer/commands - set_identity.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 311 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 6 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 <stdlib.h>
       6             : #include <unistd.h>
       7             : #include "../../platform/fd_cap_chk.h"
       8             : #include "../../../disco/keyguard/fd_keyswitch.h"
       9             : #include "../../../disco/keyguard/fd_keyload.h"
      10             : #include "../../../disco/topo/fd_topo.h"
      11             : 
      12             : #include <strings.h>
      13             : #include <unistd.h>
      14             : #include <sys/resource.h>
      15             : 
      16             : /* The process of switching identity of the validator is somewhat
      17             :    involved, to prevent it from producing torn data (for example,
      18             :    a block where half the shreds are signed by one private key, and half
      19             :    are signed by another).
      20             : 
      21             :    The process of switching is a state machine that progresses linearly
      22             :    through each of the states.  Generally, no transitions are allowed
      23             :    except direct forward steps, except in emergency recovery cases an
      24             :    operator can force the state back to unlocked.
      25             : 
      26             :    The states follow, in order. */
      27             : 
      28             : /* State 0: UNLOCKED.
      29             :      The validator is not currently in the process of switching keys. */
      30           0 : #define FD_SET_IDENTITY_STATE_UNLOCKED                 (0UL)
      31             : 
      32             : /* State 1: LOCKED
      33             :      Some client to the validator has requested a key switch.  To do so,
      34             :      it acquired an exclusive lock on the validator to prevent the
      35             :      switch potentially being interleaved with another client. */
      36           0 : #define FD_SET_IDENTITY_STATE_LOCKED                   (1UL)
      37             : 
      38             : /* State 2: LEADER_HALT_REQUESTED
      39             :      The first step in the key switch process is to pause the leader
      40             :      pipeline of the validator, preventing us from becoming leader, but
      41             :      finishing any currently in progress leader slot if there is one.
      42             :      While in this state, the validator is waiting for the leader
      43             :      pipeline to confirm that it has paused production, and is no longer
      44             :      leader.
      45             : 
      46             :      In Firedancer, this halt request goes to the Replay tile, which
      47             :      causes the tile to switch the identity key it uses to determine the
      48             :      identity's balance as well as when the validator is the leader.
      49             :      After the leader pipeline has been halted, the validator will no
      50             :      longer become a leader until the switch has been completed. */
      51           0 : #define FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED    (2UL)
      52             : 
      53             : /* State 3: LEADER_HALTED
      54             :      The Replay tile has confirmed that it has halted the leader
      55             :      pipeline, and the validator is no longer leader.  No more blocks
      56             :      will be produced until it is unhalted.  In addition, the Replay
      57             :      tile has switched its own identity key.
      58             : 
      59             :      At this point, we also have the guarantee that there are no more
      60             :      outstanding shreds that have to be signed with the old key.  Any
      61             :      tiles related to the leader pipeline that rely on the identity key
      62             :      will not be used. */
      63           0 : #define FD_SET_IDENTITY_STATE_LEADER_HALTED            (3UL)
      64             : 
      65             : /* State 4: REPLAY_HALT_REQUESTED
      66             :      Repair, Gossip, and Tower tiles will stop sending requests
      67             :      downstream to the sign tile.  This is done to avoid any mismatches
      68             :      with the identity key.  Their identity keys will be switched after
      69             :      this step.  These tiles all use the identity key to make forward
      70             :      progress on non-leader pipeline replay.
      71             : 
      72             :      These tiles use the identity key to populate messages which are
      73             :      signed by the sign tile:
      74             :        (a) Repair.  The repair tile uses the identity key as part of the
      75             :            repair protocol.  The identity key is included in and used
      76             :            for signing requests.  Because Repair uses an asnychronous
      77             :            signing mechanism, Repair will first wait until all
      78             :            outstanding sign requests have been received back from the
      79             :            sign tile before halting any new signing requests.
      80             :        (b) Gossip.  The gossip tile sends out ContactInfo messages with
      81             :            our identity key, and also uses the identity key to sign
      82             :            outgoing gossip messages.
      83             :        (c) Tower.  The tower tiles uses the identity key to generate
      84             :            vote transactions which are sent to the send tile.  These
      85             :            vote transactions are then signed downstream by the TxSend
      86             :            tile instead of having its own keyguard client. */
      87           0 : #define FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED    (4UL)
      88             : 
      89             : /* State 5: REPLAY_HALTED
      90             :      Repair, Gossip, and Tower are no longer sending requests to the
      91             :      sign tile.  Replay can keep progressing at this point. However,
      92             :      the tower tile may have an in-flight vote transaction to the TxSend
      93             :      tile that corresponds to the old identity key. */
      94           0 : #define FD_SET_IDENTITY_STATE_REPLAY_HALTED            (5UL)
      95             : 
      96             : /* State 6: SEND_FLUSH_REQUESTED
      97             :      Once the Tower tile has updated its identity key and stopped
      98             :      sending vote transactions to the TxSend tile, any in-flight vote
      99             :      transactions for the old identity key must be flushed to avoid
     100             :      being badly signed.  We also know that Tower will send no more
     101             :      vote transactions to the TxSend tile.
     102             : 
     103             :      The TxSend tile is flushed by telling it the last sequence number
     104             :      the Tower tile has produced for an outgoing vote tansaction at the
     105             :      time it was halted.  Once the TxSend tile has processed all vote
     106             :      transactions up to and including that sequence number, it will
     107             :      switch it's own identity key.  There is a guarantee that the TxSend
     108             :      tile will not request to sign any vote transactions until it is
     109             :      unhalted.  At this point, the TxSend tile will stop receving any
     110             :      new frags from the Net tile.  The reason for this is to avoid any
     111             :      QUIC callbacks that invoke key signing. */
     112           0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED   (6UL)
     113             : 
     114             : /* State 7: TXSEND_FLUSHED
     115             :      The TxSend tile confirms that it has seen and processed all votes
     116             :      up to and including the last sequence number produced by the Tower
     117             :      tile at the time it was halted.  The TxSend tile also switches its
     118             :      own identity key which is used for signing votes and establishing
     119             :      a QUIC connection.  The TxSend tile is now no longer receiving any
     120             :      new frags from the Net tile. */
     121           0 : #define FD_SET_IDENTITY_STATE_TXSEND_FLUSHED           (7UL)
     122             : 
     123             : /* State 8: ALL_SWITCH_REQUESTED
     124             :      The client now requests that all other tiles which consume the
     125             :      identity key in some way switch to the new key.  The leader
     126             :      pipeline is still halted, although it doesn't strictly need to be,
     127             :      since outgoing shreds have been flushed.  This is done to keep the
     128             :      control flow simpler.  The sign tile is switched first to avoid any
     129             :      potential mismatches with the identity key.
     130             : 
     131             :      The other tiles using the identity key are:
     132             :        (a) Sign.  The sign tile is responsible for holding the private
     133             :            key and servicing signing requests from other tiles.
     134             :        (b) GUI.  The GUI shows the validator identity key to the user,
     135             :            and uses the key to determine which blocks are ours for
     136             :            highlighting on the frontend.
     137             :        (c) Bundle.  The validator must authenticate to any connected
     138             :            bundle server with the identity key to prove it is on the
     139             :            leader schedule.
     140             :        (d) Gossvf.  The gossvf tile uses the identity key to detect
     141             :            duplicate running instances of the same validator node as
     142             :            well as other message handling.
     143             :        (e) Shred.  The shred tile uses the identity key to determine the
     144             :            position of the validator in the Turbine tree and to sign
     145             :            outgoing shreds.
     146             :        (f) Event.  Outgoing events to the event server are signed with
     147             :            the identity key to authenticate the sender. */
     148             : 
     149           0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED     (8UL)
     150             : 
     151             : /* State 9: ALL_SWITCHED
     152             :      All remaining tiles that use the identity key have confirmed that
     153             :      they have switched to the new key.  The validator is now fully
     154             :      switched over. */
     155           0 : #define FD_SET_IDENTITY_STATE_ALL_SWITCHED             (9UL)
     156             : 
     157             : /* State 10: REPLAY_UNHALT_REQUESTED
     158             :      Now that all of the tiles are using the switched identity key, the
     159             :      tiles that rely on the sign tile can be unhalted.  These are the
     160             :      same tiles from REPLAY_HALT_REQUESTED. */
     161           0 : #define FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED (10UL)
     162             : 
     163             : /* State 11: REPLAY_UNHALTED
     164             :      All tiles that rely on the sign tile have been unhalted and now the
     165             :      validator can resume making progress on replay. */
     166           0 : #define FD_SET_IDENTITY_STATE_REPLAY_UNHALTED          (11UL)
     167             : 
     168             : /* State 12: LEADER_UNHALT_REQUESTED
     169             :      The final state, now that all tiles have switched, the leader
     170             :      pipeline can be unblocked and the validator can resume producing
     171             :      blocks.  The next state once the Replay tile confirms the leader
     172             :      pipeline is unlocked, is UNLOCKED. */
     173           0 : #define FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED  (12UL)
     174             : 
     175             : void
     176             : set_identity_cmd_perm( args_t *         args   FD_PARAM_UNUSED,
     177             :                        fd_cap_chk_t *   chk,
     178           0 :                        config_t const * config FD_PARAM_UNUSED ) {
     179             :   /* 5 huge pages for the key storage area */
     180           0 :   ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
     181           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)`" );
     182           0 : }
     183             : 
     184             : static fd_keyswitch_t *
     185             : find_keyswitch( fd_topo_t const * topo,
     186           0 :                 char const *      tile_name ) {
     187           0 :   ulong tile_idx = fd_topo_find_tile( topo, tile_name, 0UL );
     188           0 :   FD_TEST( tile_idx!=ULONG_MAX );
     189           0 :   FD_TEST( topo->tiles[ tile_idx ].id_keyswitch_obj_id!=ULONG_MAX );
     190             : 
     191           0 :   fd_keyswitch_t * keyswitch = fd_topo_obj_laddr( topo, topo->tiles[ tile_idx ].id_keyswitch_obj_id );
     192           0 :   FD_TEST( keyswitch );
     193           0 :   return keyswitch;
     194           0 : }
     195             : 
     196             : static void FD_FN_SENSITIVE
     197             : poll_keyswitch( fd_topo_t * topo,
     198             :                 ulong *     state,
     199             :                 ulong *     halted_seq,
     200             :                 uchar *     keypair,
     201             :                 int         require_tower,
     202           0 :                 int         force_lock ) {
     203           0 :   switch( *state ) {
     204           0 :     case FD_SET_IDENTITY_STATE_UNLOCKED: {
     205             :       /* First update replay's keyswitch from unlocked to locked. */
     206           0 :       fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
     207           0 :       if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &replay->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
     208           0 :         *state = FD_SET_IDENTITY_STATE_LOCKED;
     209           0 :         FD_LOG_INFO(( "Locking validator identity for key switch..." ));
     210           0 :       } else {
     211           0 :         if( FD_UNLIKELY( force_lock ) ) {
     212           0 :           *state = FD_SET_IDENTITY_STATE_LOCKED;
     213           0 :           FD_LOG_WARNING(( "Another process was changing keys, but `--force` supplied. Forcing lock on validator identity for key switch..." ));
     214           0 :         } else {
     215           0 :           FD_LOG_ERR(( "Cannot set-identity because Firedancer is already in the process of switching keys. If you are not currently "
     216           0 :                        "changing the identity, it might be because an identity change was abandoned. To recover, run the `set-identity` "
     217           0 :                        "command again with the `--force` argument." ));
     218           0 :         }
     219           0 :       }
     220           0 :       break;
     221           0 :     }
     222           0 :     case FD_SET_IDENTITY_STATE_LOCKED: {
     223           0 :       fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
     224           0 :       memcpy( replay->bytes, keypair+32UL, 32UL );
     225             : 
     226           0 :       FD_COMPILER_MFENCE();
     227           0 :       replay->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     228           0 :       FD_COMPILER_MFENCE();
     229           0 :       *state = FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED;
     230           0 :       FD_LOG_INFO(( "Pausing leader pipeline for key switch..." ));
     231           0 :       break;
     232           0 :     }
     233           0 :     case FD_SET_IDENTITY_STATE_LEADER_HALT_REQUESTED: {
     234           0 :       fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
     235           0 :       if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
     236           0 :         fd_memzero_explicit( replay->bytes, 64UL );
     237           0 :         FD_COMPILER_MFENCE();
     238           0 :         *halted_seq = replay->result;
     239           0 :         *state = FD_SET_IDENTITY_STATE_LEADER_HALTED;
     240           0 :         FD_LOG_INFO(( "Leader pipeline successfully paused..." ));
     241           0 :       } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     242           0 :         FD_SPIN_PAUSE();
     243           0 :       } else {
     244           0 :         FD_LOG_ERR(( "Unexpected keyswitch state %lu", replay->state ));
     245           0 :       }
     246           0 :       break;
     247           0 :     }
     248           0 :     case FD_SET_IDENTITY_STATE_LEADER_HALTED: {
     249             :       /* Now we have to flush any in-flight and block requests from the
     250             :          repair, gossip, send, and tower tiles that need to be signed.
     251             : 
     252             :          TODO: Tower currently doesn't support running off of a tower
     253             :          file.  When support for a tower file is added, the tower file
     254             :          will need to be swapped and synced with the local state of the
     255             :          tower.  The security sandbox implications of adding another
     256             :          file descriptor need to be considered. */
     257           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     258           0 :         fd_topo_tile_t const * tile = &topo->tiles[ i ];
     259           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     260           0 :         if( strcmp( tile->name, "repair" ) &&
     261           0 :             strcmp( tile->name, "gossip" ) &&
     262           0 :             strcmp( tile->name, "tower" ) ) {
     263           0 :           continue;
     264           0 :         }
     265           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     266             : 
     267           0 :         if( !strcmp( tile->name, "tower" ) ) tile_ks->param = !!require_tower;
     268             : 
     269           0 :         memcpy( tile_ks->bytes, keypair+32UL, 32UL );
     270           0 :         FD_COMPILER_MFENCE();
     271           0 :         tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     272           0 :         FD_COMPILER_MFENCE();
     273           0 :       }
     274           0 :       *state = FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED;
     275           0 :       FD_LOG_INFO(( "Requesting to halt all signers..." ));
     276           0 :       break;
     277           0 :     }
     278           0 :     case FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED: {
     279           0 :       int all_switched = 1;
     280           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     281           0 :         fd_topo_tile_t const * tile = &topo->tiles[ i ];
     282           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     283           0 :         if( strcmp( tile->name, "repair" )!=0 &&
     284           0 :             strcmp( tile->name, "gossip" )!=0 &&
     285           0 :             strcmp( tile->name, "tower" )!=0 ) {
     286           0 :           continue;
     287           0 :         }
     288             : 
     289           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     290           0 :         if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     291           0 :           all_switched = 0UL;
     292           0 :           break;
     293           0 :         }
     294           0 :       }
     295           0 :       if( FD_LIKELY( all_switched ) ) {
     296           0 :         FD_LOG_INFO(( "All successfully switched identity key..." ));
     297           0 :         *state = FD_SET_IDENTITY_STATE_REPLAY_HALTED;
     298           0 :       } else {
     299           0 :         FD_SPIN_PAUSE();
     300           0 :       }
     301           0 :       break;
     302           0 :     }
     303           0 :     case FD_SET_IDENTITY_STATE_REPLAY_HALTED: {
     304           0 :       ulong tower_halted_seq = find_keyswitch( topo, "tower" )->result;
     305           0 :       fd_keyswitch_t * txsend = find_keyswitch( topo, "txsend" );
     306           0 :       txsend->param = tower_halted_seq;
     307           0 :       memcpy( txsend->bytes, keypair+32UL, 32UL );
     308           0 :       FD_COMPILER_MFENCE();
     309           0 :       txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     310           0 :       FD_COMPILER_MFENCE();
     311             : 
     312           0 :       *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED;
     313           0 :       break;
     314           0 :     }
     315           0 :     case FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED: {
     316           0 :       fd_keyswitch_t * txsend = find_keyswitch( topo, "txsend" );
     317           0 :       if( FD_LIKELY( txsend->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
     318           0 :         fd_memzero_explicit( txsend->bytes, 64UL );
     319           0 :         FD_COMPILER_MFENCE();
     320           0 :         *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSHED;
     321           0 :       } else {
     322           0 :         FD_SPIN_PAUSE();
     323           0 :       }
     324           0 :       break;
     325           0 :     }
     326           0 :     case FD_SET_IDENTITY_STATE_TXSEND_FLUSHED: {
     327           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     328           0 :         fd_topo_tile_t * tile = &topo->tiles[ i ];
     329           0 :         if( strcmp( tile->name, "sign" ) ) continue;
     330           0 :         fd_keyswitch_t * sign = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id );
     331           0 :         memcpy( sign->bytes, keypair, 64UL );
     332           0 :         FD_COMPILER_MFENCE();
     333           0 :         sign->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     334           0 :         FD_COMPILER_MFENCE();
     335           0 :       }
     336             : 
     337           0 :       FD_COMPILER_MFENCE();
     338           0 :       fd_memzero_explicit( keypair, 32UL ); /* Private key no longer needed in this process */
     339           0 :       FD_COMPILER_MFENCE();
     340             : 
     341           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     342           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     343           0 :         if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ||
     344           0 :                        !strcmp( topo->tiles[ i ].name, "replay" ) ||
     345           0 :                        !strcmp( topo->tiles[ i ].name, "shred" ) ||
     346           0 :                        !strcmp( topo->tiles[ i ].name, "repair" ) ||
     347           0 :                        !strcmp( topo->tiles[ i ].name, "gossip" ) ||
     348           0 :                        !strcmp( topo->tiles[ i ].name, "txsend" ) ||
     349           0 :                        !strcmp( topo->tiles[ i ].name, "tower" ) ) ) continue;
     350             : 
     351           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     352           0 :         memcpy( tile_ks->bytes, keypair+32UL, 32UL );
     353           0 :         FD_COMPILER_MFENCE();
     354           0 :         tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     355           0 :         FD_COMPILER_MFENCE();
     356           0 :       }
     357             : 
     358           0 :       FD_LOG_INFO(( "Requesting all remaining tiles switch identity key..." ));
     359           0 :       *state = FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED;
     360           0 :       break;
     361           0 :     }
     362           0 :     case FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED: {
     363           0 :       ulong all_switched = 1UL;
     364           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     365           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     366           0 :         if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "replay" ) ||
     367           0 :                        !strcmp( topo->tiles[ i ].name, "shred" ) ||
     368           0 :                        !strcmp( topo->tiles[ i ].name, "repair" ) ||
     369           0 :                        !strcmp( topo->tiles[ i ].name, "gossip" ) ||
     370           0 :                        !strcmp( topo->tiles[ i ].name, "txsend" ) ||
     371           0 :                        !strcmp( topo->tiles[ i ].name, "tower" ) ) ) continue;
     372             : 
     373           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     374           0 :         if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     375           0 :           all_switched = 0UL;
     376           0 :           break;
     377           0 :         } else if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
     378           0 :           if( FD_LIKELY( !strcmp( topo->tiles[ i ].name, "sign" ) ) ) {
     379           0 :             FD_COMPILER_MFENCE();
     380           0 :             fd_memzero_explicit( tile_ks->bytes, 64UL );
     381           0 :             FD_COMPILER_MFENCE();
     382           0 :           }
     383           0 :           continue;
     384           0 :         } else {
     385           0 :           FD_LOG_ERR(( "Unexpected %s keyswitch state %lu", topo->tiles[ i ].name, tile_ks->state ));
     386           0 :         }
     387           0 :       }
     388             : 
     389           0 :       if( FD_LIKELY( all_switched ) ) {
     390           0 :         FD_LOG_INFO(( "All tiles successfully switched identity key..." ));
     391           0 :         *state = FD_SET_IDENTITY_STATE_ALL_SWITCHED;
     392           0 :       } else {
     393           0 :         FD_SPIN_PAUSE();
     394           0 :       }
     395           0 :       break;
     396           0 :     }
     397           0 :     case FD_SET_IDENTITY_STATE_ALL_SWITCHED: {
     398           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     399           0 :         fd_topo_tile_t const * tile = &topo->tiles[ i ];
     400           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     401           0 :         if( strcmp( tile->name, "repair" ) &&
     402           0 :             strcmp( tile->name, "gossip" ) &&
     403           0 :             strcmp( tile->name, "tower" ) &&
     404           0 :             strcmp( tile->name, "txsend" ) ) {
     405           0 :           continue;
     406           0 :         }
     407             : 
     408           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     409           0 :         FD_COMPILER_MFENCE();
     410           0 :         tile_ks->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
     411           0 :         FD_COMPILER_MFENCE();
     412           0 :       }
     413             : 
     414           0 :       FD_LOG_INFO(( "Requesting to unpause signers..." ));
     415           0 :       *state = FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED;
     416           0 :       break;
     417           0 :     }
     418           0 :     case FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED: {
     419           0 :       int all_switched = 1;
     420           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     421           0 :         fd_topo_tile_t const * tile = &topo->tiles[ i ];
     422           0 :         if( FD_LIKELY( topo->tiles[ i ].id_keyswitch_obj_id==ULONG_MAX ) ) continue;
     423           0 :         if( strcmp( tile->name, "repair" ) &&
     424           0 :             strcmp( tile->name, "gossip" ) &&
     425           0 :             strcmp( tile->name, "tower" ) &&
     426           0 :             strcmp( tile->name, "txsend" ) ) {
     427           0 :           continue;
     428           0 :         }
     429             : 
     430           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].id_keyswitch_obj_id );
     431           0 :         if( FD_LIKELY( tile_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
     432           0 :           all_switched = 0UL;
     433           0 :           break;
     434           0 :         }
     435           0 :       }
     436           0 :       if( FD_LIKELY( all_switched ) ) {
     437           0 :         FD_LOG_INFO(( "Successfully unpaused all non-leader signers..." ));
     438           0 :         *state = FD_SET_IDENTITY_STATE_REPLAY_UNHALTED;
     439           0 :       } else {
     440           0 :         FD_SPIN_PAUSE();
     441           0 :       }
     442           0 :       break;
     443           0 :     }
     444           0 :     case FD_SET_IDENTITY_STATE_REPLAY_UNHALTED: {
     445           0 :       fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
     446           0 :       replay->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
     447           0 :       FD_LOG_INFO(( "Requesting to unpause leader pipeline..." ));
     448           0 :       *state = FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED;
     449           0 :       break;
     450           0 :     }
     451           0 :     case FD_SET_IDENTITY_STATE_LEADER_UNHALT_REQUESTED: {
     452           0 :       fd_keyswitch_t * replay = find_keyswitch( topo, "replay" );
     453           0 :       if( FD_LIKELY( replay->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
     454           0 :         FD_LOG_INFO(( "Leader pipeline unpaused..." ));
     455           0 :         replay->state = FD_KEYSWITCH_STATE_UNLOCKED;
     456           0 :         *state = FD_SET_IDENTITY_STATE_UNLOCKED;
     457           0 :       } else if( FD_UNLIKELY( replay->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
     458           0 :         FD_SPIN_PAUSE();
     459           0 :       } else {
     460           0 :         FD_LOG_ERR(( "Unexpected replay keyswitch state %lu", replay->state ));
     461           0 :       }
     462           0 :       break;
     463           0 :     }
     464           0 :   }
     465           0 : }
     466             : 
     467             : void
     468             : set_identity_cmd_args( int *    pargc,
     469             :                        char *** pargv,
     470           0 :                        args_t * args) {
     471           0 :   args->set_identity.require_tower = fd_env_strip_cmdline_contains( pargc, pargv, "--require-tower" );
     472           0 :   args->set_identity.force         = fd_env_strip_cmdline_contains( pargc, pargv, "--force" );
     473             : 
     474           0 :   if( FD_UNLIKELY( *pargc<1 ) ) goto err;
     475             : 
     476           0 :   char const * path = *pargv[0];
     477           0 :   (*pargc)--;
     478           0 :   (*pargv)++;
     479             : 
     480           0 :   if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
     481           0 :     args->set_identity.keypair = fd_keyload_alloc_protected_pages( 1UL, 2UL );
     482           0 :     FD_LOG_STDOUT(( "Reading identity keypair from stdin.  Press Ctrl-D when done.\n" ));
     483           0 :     fd_keyload_read( STDIN_FILENO, "stdin", args->set_identity.keypair );
     484           0 :   } else {
     485           0 :     args->set_identity.keypair = fd_keyload_load( path, 0 );
     486           0 :   }
     487             : 
     488           0 :   return;
     489             : 
     490           0 : err:
     491           0 :   FD_LOG_ERR(( "Usage: firedancer set-identity <keypair> [--require-tower] [--force]" ));
     492           0 : }
     493             : 
     494             : static void FD_FN_SENSITIVE
     495             : set_identity( args_t *   args,
     496           0 :               config_t * config ) {
     497           0 :   uchar check_public_key[ 32 ];
     498           0 :   fd_sha512_t sha512[1];
     499           0 :   FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
     500           0 :   fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
     501           0 :   if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
     502           0 :     FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
     503           0 :                  "Firedancer will not use the key pair to sign as it might leak the private key." ));
     504             : 
     505           0 :   for( ulong i=0UL; i<config->topo.obj_cnt; i++ ) {
     506           0 :     fd_topo_obj_t * obj = &config->topo.objs[ i ];
     507           0 :     if( FD_LIKELY( strcmp( obj->name, "keyswitch" ) ) ) continue;
     508             : 
     509           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 );
     510           0 :   }
     511             : 
     512           0 :   ulong state = FD_SET_IDENTITY_STATE_UNLOCKED;
     513           0 :   ulong halted_seq = 0UL;
     514           0 :   for(;;) {
     515           0 :     poll_keyswitch( &config->topo, &state, &halted_seq, args->set_identity.keypair, args->set_identity.require_tower, args->set_identity.force );
     516           0 :     if( FD_UNLIKELY( FD_SET_IDENTITY_STATE_UNLOCKED==state ) ) break;
     517           0 :   }
     518             : 
     519           0 :   char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
     520           0 :   fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
     521           0 :   identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
     522             : 
     523           0 :   FD_LOG_NOTICE(( "Validator identity key switched to `%s`", identity_key_base58 ));
     524           0 : }
     525             : 
     526             : void
     527             : set_identity_cmd_fn( args_t *   args,
     528           0 :                      config_t * config ) {
     529           0 :   set_identity( args, config );
     530           0 : }
     531             : 
     532             : action_t fd_action_set_identity = {
     533             :   .name           = "set-identity",
     534             :   .args           = set_identity_cmd_args,
     535             :   .fn             = set_identity_cmd_fn,
     536             :   .require_config = 1,
     537             :   .perm           = set_identity_cmd_perm,
     538             :   .description    = "Change the identity of a running validator",
     539             : };

Generated by: LCOV version 1.14