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 :
12 : #include <strings.h>
13 : #include <unistd.h>
14 : #include <sys/resource.h>
15 :
16 : void
17 : set_identity_cmd_perm( args_t * args FD_PARAM_UNUSED,
18 : fd_cap_chk_t * chk,
19 0 : config_t const * config FD_PARAM_UNUSED ) {
20 : /* 5 huge pages for the key storage area */
21 0 : ulong mlock_limit = 5UL * FD_SHMEM_NORMAL_PAGE_SZ;
22 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)`" );
23 0 : }
24 :
25 : void
26 : set_identity_cmd_args( int * pargc,
27 : char *** pargv,
28 0 : args_t * args) {
29 :
30 0 : char const * name = fd_env_strip_cmdline_cstr( pargc, pargv, "--name", NULL, NULL );
31 0 : if( FD_UNLIKELY( name ) ) fd_cstr_ncpy( args->set_identity.name, name, sizeof(args->set_identity.name) );
32 :
33 0 : if( FD_UNLIKELY( *pargc<1 ) ) goto err;
34 :
35 0 : char const * path = *pargv[0];
36 0 : (*pargc)--;
37 0 : (*pargv)++;
38 :
39 0 : if( FD_UNLIKELY( !strcmp( path, "-" ) ) ) {
40 0 : uchar * keypair_wr = fd_keyload_alloc_protected_pages( 1UL, 2UL );
41 0 : FD_LOG_STDOUT(( "Reading identity keypair from stdin. Press Ctrl-D when done.\n" ));
42 0 : fd_keyload_read( STDIN_FILENO, "stdin", keypair_wr );
43 0 : args->set_identity.keypair = fd_keyload_mprotect_ro( keypair_wr, 0 );
44 0 : } else {
45 0 : args->set_identity.keypair = fd_keyload_load( path, 0 );
46 0 : }
47 :
48 0 : return;
49 :
50 0 : err:
51 0 : FD_LOG_ERR(( "Usage: %s set-identity <keypair>", FD_BINARY_NAME ));
52 0 : }
53 :
54 : static void FD_FN_SENSITIVE
55 : set_identity( args_t * args,
56 0 : config_t * config ) {
57 0 : uchar check_public_key[ 32 ];
58 0 : fd_sha512_t sha512[ 1 ];
59 0 : FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
60 0 : fd_ed25519_public_from_private( check_public_key, args->set_identity.keypair, sha512 );
61 0 : if( FD_UNLIKELY( memcmp( check_public_key, args->set_identity.keypair+32UL, 32UL ) ) )
62 0 : FD_LOG_ERR(( "The public key in the identity key file does not match the public key derived from the private key. "
63 0 : "Firedancer will not use the key pair to sign as it might leak the private key." ));
64 :
65 0 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
66 0 : fd_base58_encode_32( args->set_identity.keypair+32UL, NULL, identity_key_base58 );
67 0 : identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
68 :
69 0 : fd_adminctl_t * adminctl = adminctl_client_attach( config, args->set_identity.name );
70 :
71 0 : void * payload = NULL;
72 0 : ulong payload_max = 0UL;
73 0 : ulong slot_idx = fd_adminctl_reserve( adminctl, &payload, &payload_max );
74 0 : if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) {
75 0 : FD_LOG_ERR(( "Failed to process `set-identity` command as there are other pending "
76 0 : "commands that are being processed. Please wait for other commands to complete "
77 0 : "or forcefully terminate the other processes and retry the command." ));
78 0 : }
79 0 : if( FD_UNLIKELY( sizeof(fd_adminctl_set_identity_t)>payload_max ) ) FD_LOG_ERR(( "adminctl set-identity payload too large" ));
80 :
81 0 : fd_adminctl_set_identity_t * req = (fd_adminctl_set_identity_t *)payload;
82 0 : req->version = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION;
83 0 : memcpy( req->keypair, args->set_identity.keypair, 64UL );
84 :
85 0 : uchar * keypair_wr = fd_keyload_mprotect_wr( args->set_identity.keypair, 0 );
86 0 : fd_memzero_explicit( keypair_wr, 64UL );
87 0 : fd_keyload_mprotect_ro( keypair_wr, 0 );
88 :
89 0 : fd_adminctl_publish( adminctl, slot_idx, FD_ADMINCTL_CMD_SET_IDENTITY, sizeof(fd_adminctl_set_identity_t) );
90 :
91 0 : ulong result = fd_adminctl_wait( adminctl, slot_idx );
92 0 : switch( result ) {
93 0 : case FD_ADMINCTL_RESULT_SUCCESS:
94 0 : FD_LOG_NOTICE(( "validator identity key switched to %s%s%s", fd_log_style_bold(), identity_key_base58, fd_log_style_normal() ));
95 0 : break;
96 0 : case FD_ADMINCTL_RESULT_UNKNOWN_COMMAND:
97 0 : case FD_SET_IDENTITY_RESULT_PAYLOAD_TOO_SMALL:
98 0 : case FD_SET_IDENTITY_RESULT_UNSUPPORTED_PAYLOAD_VERSION:
99 0 : case FD_SET_IDENTITY_RESULT_UNEXPECTED_PAYLOAD_SIZE:
100 0 : case FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH:
101 0 : FD_LOG_ERR(( "Failed to set identity: the command was not able to successfully communicate "
102 0 : "with the running Firedancer process. It is possible that you are running the "
103 0 : "command from an older or newer version of Firedancer that is no longer compatible." ));
104 0 : default:
105 0 : FD_LOG_ERR(( "Unexpected set-identity result %lu. This can be a result of a version mismatch "
106 0 : "between the command and the running Firedancer process. Please report this to the "
107 0 : "Firedancer team for investigation.", result ));
108 0 : }
109 0 : }
110 :
111 : void
112 : set_identity_cmd_fn( args_t * args,
113 0 : config_t * config ) {
114 0 : set_identity( args, config );
115 0 : }
116 :
117 : static void
118 0 : set_identity_args_help( fd_action_help_t * help ) {
119 : fd_action_help_arg( help, "<keypair>", NULL, "Path to the new identity keypair, in the standard Solana keypair file\n"
120 0 : "format (the 64-byte JSON array). Pass `-` to read the same JSON\n"
121 0 : "array from stdin instead of from a file" );
122 0 : fd_action_help_arg( help, "--name", "<name>", "Name of the validator instance to attach to, if more than one is\n"
123 0 : "running on this host" );
124 0 : }
125 :
126 : action_t fd_action_set_identity = {
127 : .name = "set-identity",
128 : .args = set_identity_cmd_args,
129 : .fn = set_identity_cmd_fn,
130 : .require_config = 0,
131 : .perm = set_identity_cmd_perm,
132 : .description = "Change the identity of a running validator",
133 : .detail = "Switches the gossip/voting/block-production identity key of an already\n"
134 : "running validator to the keypair you provide, without restarting it. The\n"
135 : "switch is atomic: the validator briefly pauses block production so it never\n"
136 : "signs with a mix of the old and new keys, then resumes under the new\n"
137 : "identity. On success it prints `Validator identity key switched to <pubkey>`\n"
138 : "and exits 0; on any error it exits non-zero and the identity is unchanged.\n"
139 : "\n"
140 : "This command does not start a validator; it attaches to one that is already\n"
141 : "running. With no arguments it discovers the running validator automatically.\n"
142 : "If multiple validators are running, pass --name to select one. If --config is\n"
143 : "given, the validator is instead located from the configuration file; only the\n"
144 : "name and [hugetlbfs.mount_path] values are used, and they must match the\n"
145 : "running validator.\n"
146 : "\n"
147 : "The change is live only: it is not written back to the config file, so the\n"
148 : "validator reverts to the configured [paths.identity_key] on its next restart.\n"
149 : "To make the new identity permanent, also update that path in the config.\n",
150 : .usage = "set-identity <keypair> [--name <name>]",
151 : .args_help = set_identity_args_help,
152 : };
|