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 : };