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