LCOV - code coverage report
Current view: top level - app/firedancer/commands - add_authorized_voter.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 141 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 4 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 "../../../disco/topo/fd_topo.h"
       6             : #include "../../../disco/keyguard/fd_keyswitch.h"
       7             : #include "../../../disco/keyguard/fd_keyload.h"
       8             : 
       9             : #include <strings.h>
      10             : #include <unistd.h>
      11             : 
      12             : /* The process of adding an authorized voter to the validator must be
      13             :    done carefully in order to prevent vote transactions being generated
      14             :    with an authorized voter that the sign tile is not yet aware of.
      15             :    The authorized voter must be added to the sign tile before it is
      16             :    added to the tower tile.  All transitions must be linear and in
      17             :    forward order.  The states below describe the state transitions.
      18             : 
      19             :    The caller should expect the command to fail if:
      20             :    1. The authorized voter keypair being passed in is already part of
      21             :       the authorized voter set.
      22             :    2. There are too many authorized voters being passed in. */
      23             : 
      24             : /* State 0: UNLOCKED.
      25             :      The validator is not currently in the process of switching keys. */
      26           0 : #define FD_ADD_AUTH_VOTER_STATE_UNLOCKED             (0UL)
      27             : 
      28             : /* State 1: LOCKED
      29             :      Some client to the validator has requested to add an authorized
      30             :      voter.  To do so, it acquired an exclusive lock on the validator to
      31             :      prevent the switch potentially being interleaved with another
      32             :      client. */
      33           0 : #define FD_ADD_AUTH_VOTER_STATE_LOCKED               (1UL)
      34             : 
      35             : /* State 2: SIGN_TILE_REQUESTED
      36             :      The first step to add an authorized voter is to notify the sign
      37             :      tile that an authorized voter is being added. */
      38           0 : #define FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED  (2UL)
      39             : 
      40             : /* State 3: SIGN_TILE_UPDATED
      41             :      The Sign tile has confirmed that it has updated its internal
      42             :      mapping for the set of supported authorized voters.  At this point
      43             :      the sign tile is aware of the new authorized voter but the Tower
      44             :      tile will not prepare vote transactions with the new authorized
      45             :      voter yet. */
      46           0 : #define FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED    (3UL)
      47             : 
      48             : /* State 4: TOWER_TILE_REQUESTED
      49             :      Once the Sign tile is updated, now the Tower tile must be notified
      50             :      that an authorized voter is being added so it can start preparing
      51             :      vote transactions with the new authorized voter. */
      52           0 : #define FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED (4UL)
      53             : 
      54             : /* State 5: TOWER_TILE_UPDATED
      55             :      The Tower tile has confirmed that it has updated its internal
      56             :      mapping for the set of supported authorized voters. */
      57           0 : #define FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED   (5UL)
      58             : 
      59             : /* State 6: UNLOCK_REQUESTED
      60             :      The client now requests that the Tower tile unpause the pipeline
      61             :      so the validator can start producing votes with the new authorized
      62             :      voter. */
      63           0 : #define FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED     (6UL)
      64             : 
      65             : void
      66             : add_authorized_voter_cmd_args( int *    pargc,
      67             :                                char *** pargv,
      68           0 :                                args_t * args ) {
      69             : 
      70           0 :   if( FD_UNLIKELY( *pargc<1 ) ) {
      71           0 :     FD_LOG_ERR(( "Usage: firedancer add-authorized-voter <keypair>" ));
      72           0 :   }
      73             : 
      74           0 :   char const * path = *pargv[0];
      75           0 :   (*pargc)--;
      76           0 :   (*pargv)++;
      77             : 
      78           0 :   if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
      79           0 :     args->add_authorized_voter.keypair = fd_keyload_alloc_protected_pages( 1UL, 2UL );
      80           0 :     FD_LOG_STDOUT(( "Reading authorized voter keypair from stdin.  Press Ctrl-D when done.\n" ));
      81           0 :     fd_keyload_read( STDIN_FILENO, "stdin", args->add_authorized_voter.keypair );
      82           0 :   } else {
      83           0 :     args->add_authorized_voter.keypair = fd_keyload_load( path, 0 );
      84           0 :   }
      85           0 : }
      86             : 
      87             : static void FD_FN_SENSITIVE
      88             : poll_keyswitch( fd_topo_t * topo,
      89             :                 ulong *     state,
      90             :                 uchar *     keypair,
      91           0 :                 int *       has_error ) {
      92           0 :   fd_keyswitch_t * tower = fd_topo_obj_laddr( topo, topo->tiles[ fd_topo_find_tile( topo, "tower", 0UL ) ].av_keyswitch_obj_id );
      93             : 
      94           0 :   switch( *state ) {
      95           0 :     case FD_ADD_AUTH_VOTER_STATE_UNLOCKED: {
      96           0 :       if( FD_LIKELY( FD_KEYSWITCH_STATE_UNLOCKED==FD_ATOMIC_CAS( &tower->state, FD_KEYSWITCH_STATE_UNLOCKED, FD_KEYSWITCH_STATE_LOCKED ) ) ) {
      97           0 :         *state = FD_ADD_AUTH_VOTER_STATE_LOCKED;
      98           0 :         FD_LOG_INFO(( "Locking authorized voter set for authorized voter update..." ));
      99           0 :       } else {
     100           0 :         FD_LOG_ERR(( "Cannot add-authorized-voter because Firedancer is already in the process of updating the authorized voter keys. If you "
     101           0 :                      "are not currently adding an authorized voter, it might be because an authorized voter update was abandoned." ));
     102           0 :       }
     103           0 :       break;
     104           0 :     }
     105           0 :     case FD_ADD_AUTH_VOTER_STATE_LOCKED: {
     106           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     107           0 :         if( FD_LIKELY( strcmp( topo->tiles[ i ].name, "sign" ) ) ) continue;
     108           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].av_keyswitch_obj_id );
     109           0 :         memcpy( tile_ks->bytes, keypair, 64UL );
     110           0 :         FD_COMPILER_MFENCE();
     111           0 :         tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     112           0 :         FD_COMPILER_MFENCE();
     113           0 :       }
     114           0 :       fd_memzero_explicit( keypair, 32UL );
     115           0 :       *state = FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED;
     116           0 :       FD_LOG_INFO(( "Requesting all sign tiles to update authorized voter key set..." ));
     117           0 :       break;
     118           0 :     }
     119           0 :     case FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_REQUESTED: {
     120           0 :       int all_updated = 1;
     121           0 :       for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     122           0 :         if( FD_LIKELY( strcmp( topo->tiles[ i ].name, "sign" ) ) ) continue;
     123           0 :         fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, topo->tiles[ i ].av_keyswitch_obj_id );
     124           0 :         if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     125           0 :           all_updated = 0;
     126           0 :           break;
     127           0 :         } else if( FD_UNLIKELY( tile_ks->state==FD_KEYSWITCH_STATE_FAILED ) ) {
     128           0 :           fd_memzero_explicit( tile_ks->bytes, 64UL );
     129           0 :           *has_error  = 1;
     130           0 :           break;
     131           0 :         } else {
     132           0 :           fd_memzero_explicit( tile_ks->bytes, 64UL );
     133           0 :         }
     134           0 :       }
     135             : 
     136           0 :       if( FD_LIKELY( all_updated ) ) {
     137           0 :         if( FD_UNLIKELY( *has_error ) ) *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED;
     138           0 :         else                            *state = FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED;
     139           0 :       } else {
     140           0 :         FD_SPIN_PAUSE();
     141           0 :       }
     142           0 :       break;
     143           0 :     }
     144           0 :     case FD_ADD_AUTH_VOTER_STATE_SIGN_TILE_UPDATED: {
     145           0 :       memcpy( tower->bytes, keypair+32UL, 32UL );
     146           0 :       FD_COMPILER_MFENCE();
     147           0 :       tower->state = FD_KEYSWITCH_STATE_SWITCH_PENDING;
     148           0 :       FD_COMPILER_MFENCE();
     149           0 :       *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED;
     150           0 :       FD_LOG_INFO(( "Requesting tower tile to update authorized voter key set..." ));
     151           0 :       break;
     152           0 :     }
     153           0 :     case FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_REQUESTED: {
     154             :       /* There is a guarantee that the tower tile will be in sync with
     155             :          the set of authorized voters in the sign tile.  At this point
     156             :          that means that the command should succeed because invariants
     157             :          such as not having duplicate authorized voter keys and too many
     158             :          authorized voters are upheld.  If this doesn't hold true, the
     159             :          Tower tile will detect any corruption and gracefully crash the
     160             :          validator. */
     161           0 :       if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_COMPLETED ) ) {
     162           0 :         *state = FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED;
     163           0 :         FD_LOG_INFO(( "Tower tile key set successfully updated..." ));
     164           0 :       } else {
     165           0 :         FD_SPIN_PAUSE();
     166           0 :       }
     167           0 :       break;
     168           0 :     }
     169           0 :     case FD_ADD_AUTH_VOTER_STATE_TOWER_TILE_UPDATED: {
     170           0 :       tower->state = FD_KEYSWITCH_STATE_UNHALT_PENDING;
     171           0 :       *state = FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED;
     172           0 :       FD_LOG_INFO(( "Requesting tower tile to unlock authorized voter key set..." ));
     173           0 :       break;
     174           0 :     }
     175           0 :     case FD_ADD_AUTH_VOTER_STATE_UNLOCK_REQUESTED: {
     176           0 :       if( FD_LIKELY( tower->state==FD_KEYSWITCH_STATE_UNLOCKED ) ) {
     177           0 :         *state = FD_ADD_AUTH_VOTER_STATE_UNLOCKED;
     178           0 :         FD_LOG_INFO(( "Authorized voter key set unlocked..." ));
     179           0 :       } else {
     180           0 :         FD_SPIN_PAUSE();
     181           0 :       }
     182           0 :       break;
     183           0 :     }
     184           0 :     default: {
     185           0 :       FD_LOG_ERR(( "Unexpected state %lu", *state ));
     186           0 :     }
     187           0 :   }
     188           0 : }
     189             : 
     190             : static void FD_FN_SENSITIVE
     191             : add_authorized_voter( args_t *   args,
     192           0 :                       config_t * config ) {
     193           0 :   uchar check_public_key[ 32 ];
     194           0 :   fd_sha512_t sha512[1];
     195           0 :   FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
     196             : 
     197           0 :   fd_ed25519_public_from_private( check_public_key, args->add_authorized_voter.keypair, sha512 );
     198           0 :   if( FD_UNLIKELY( memcmp( check_public_key, args->add_authorized_voter.keypair+32UL, 32UL ) ) ) {
     199           0 :     FD_LOG_ERR(( "The public key in the key file does not match the public key derived from the private key."
     200           0 :                  "Firedancer will not use the key pair to sign as it might leak the private key." ));
     201           0 :   }
     202             : 
     203           0 :   for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
     204           0 :     fd_topo_tile_t * tile = &config->topo.tiles[ i ];
     205           0 :     if( FD_LIKELY( tile->av_keyswitch_obj_id==ULONG_MAX ) ) continue;
     206           0 :     fd_topo_obj_t * obj = &config->topo.objs[ tile->av_keyswitch_obj_id ];
     207           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 );
     208           0 :   }
     209             : 
     210           0 :   int has_error = 0;
     211           0 :   ulong state = FD_ADD_AUTH_VOTER_STATE_UNLOCKED;
     212           0 :   for(;;) {
     213           0 :     poll_keyswitch( &config->topo, &state, args->add_authorized_voter.keypair, &has_error );
     214           0 :     if( FD_UNLIKELY( FD_ADD_AUTH_VOTER_STATE_UNLOCKED==state ) ) break;
     215           0 :   }
     216             : 
     217           0 :   char key_base58[ FD_BASE58_ENCODED_32_SZ ];
     218           0 :   fd_base58_encode_32( args->add_authorized_voter.keypair+32UL, NULL, key_base58 );
     219           0 :   key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
     220             : 
     221           0 :   if( FD_UNLIKELY( has_error ) ) FD_LOG_ERR(( "Failed to add authorized voter key to `%s`, check validator logs for details", key_base58 ));
     222           0 :   else                           FD_LOG_NOTICE(( "Authorized voter key added `%s`", key_base58 ));
     223             : 
     224           0 : }
     225             : 
     226             : void
     227             : add_authorized_voter_cmd_fn( args_t *   args,
     228           0 :                              config_t * config ) {
     229           0 :   add_authorized_voter( args, config );
     230           0 : }
     231             : 
     232             : action_t fd_action_add_authorized_voter = {
     233             :   .name           = "add-authorized-voter",
     234             :   .args           = add_authorized_voter_cmd_args,
     235             :   .fn             = add_authorized_voter_cmd_fn,
     236             :   .require_config = 1,
     237             :   .perm           = NULL,
     238             :   .description    = "Add an authorized voter to the validator",
     239             : };

Generated by: LCOV version 1.14