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 : #include "../../../discof/admin/fd_adminctl.h"
6 : #include "../../../disco/topo/fd_dns_resolve.h"
7 : #include "../../../disco/keyguard/fd_keyload.h"
8 : #include "../../../ballet/base58/fd_base58.h"
9 : #include "../../../ballet/ed25519/fd_ed25519.h"
10 : #include "../../../util/net/fd_ip4.h"
11 : #include "../../platform/fd_cap_chk.h"
12 :
13 : #include <errno.h>
14 : #include <stdio.h>
15 : #include <stdlib.h>
16 : #include <unistd.h>
17 : #include <sys/resource.h>
18 :
19 : /* Strips key and its value. */
20 : static char const *
21 : strip_value( int * pargc,
22 : char *** pargv,
23 0 : char const * key ) {
24 0 : for( int i=0; i<*pargc; i++ ) {
25 0 : if( FD_UNLIKELY( !strcmp( (*pargv)[ i ], key ) && ( i+1==*pargc || !strncmp( (*pargv)[ i+1 ], "--", 2UL ) ) ) ) {
26 0 : FD_LOG_ERR(( "`%s` needs a value", key ));
27 0 : }
28 0 : }
29 0 : return fd_env_strip_cmdline_cstr( pargc, pargv, key, NULL, NULL );
30 0 : }
31 :
32 : /* Fetch and parse CLI args (Helper). */
33 : static void
34 : failover_args( int * pargc,
35 : char *** pargv,
36 0 : args_t * args ) {
37 0 : if( FD_UNLIKELY( !*pargc || ( strcmp( (*pargv)[ 0 ], "promote" ) && strcmp( (*pargv)[ 0 ], "takeover" ) ) ) ) {
38 0 : FD_LOG_ERR(( "usage: firedancer failover <promote|takeover> [<keypair>] [OPTIONS]" ));
39 0 : }
40 0 : args->failover.takeover = !strcmp( (*pargv)[ 0 ], "takeover" );
41 0 : (*pargc)--;
42 0 : (*pargv)++;
43 0 : char const * name = strip_value( pargc, pargv, "--name" );
44 0 : char const * address = strip_value( pargc, pargv, "--address" );
45 0 : char const * port = strip_value( pargc, pargv, "--port" );
46 0 : if( FD_UNLIKELY( name ) ) fd_cstr_ncpy( args->failover.name, name, sizeof(args->failover.name) );
47 0 : args->failover.yes = fd_env_strip_cmdline_contains( pargc, pargv, "--yes" );
48 0 : args->failover.force = fd_env_strip_cmdline_contains( pargc, pargv, "--force" );
49 :
50 0 : if( FD_UNLIKELY( args->failover.force && !args->failover.takeover ) ) {
51 0 : FD_LOG_ERR(( "--force goes with `failover takeover`, `failover promote` hands the identity over cleanly" ));
52 0 : }
53 0 : if( FD_UNLIKELY( address && !fd_dns_resolve_address( address, &args->failover.addr ) ) ) {
54 0 : FD_LOG_ERR(( "could not resolve --address `%s` to an IPv4 address", address ));
55 0 : }
56 0 : if( FD_UNLIKELY( address && !args->failover.addr ) ) {
57 0 : FD_LOG_ERR(( "--address `%s` is 0.0.0.0, provide the address of the machine running the identity", address ));
58 0 : }
59 0 : if( port ) {
60 0 : uint value = 0U;
61 0 : for( char const * c=port; *c; c++ ) {
62 0 : uint digit = (uint)( *c-'0' );
63 0 : if( FD_UNLIKELY( digit>9U || value*10U+digit>65535U ) ) {
64 0 : FD_LOG_ERR(( "--port `%s` must be a port number from 1 to 65535", port ));
65 0 : }
66 0 : value = value*10U+digit;
67 0 : }
68 0 : if( FD_UNLIKELY( !value ) ) {
69 0 : FD_LOG_ERR(( "--port `%s` must be a port number from 1 to 65535", port ));
70 0 : }
71 0 : args->failover.port = (ushort)value;
72 0 : }
73 0 : if( *pargc && ( (*pargv)[ 0 ][ 0 ]!='-' || !strcmp( (*pargv)[ 0 ], "-" ) ) ) {
74 0 : fd_cstr_ncpy( args->failover.keypair_path, (*pargv)[ 0 ], sizeof(args->failover.keypair_path) );
75 0 : (*pargc)--;
76 0 : (*pargv)++;
77 0 : }
78 0 : }
79 :
80 : static void
81 0 : failover_confirm( args_t const * args ) {
82 0 : if( args->failover.force ) {
83 0 : FD_LOG_STDOUT(( "This asks the machine running the identity to hand it over. If that fails for any\n"
84 0 : "reason, this machine takes the identity anyway.\n"
85 0 : "WARNING: --force skips every check on the other machine, which may still run the\n"
86 0 : "identity. Use it only when that machine is not signing votes, or both machines\n"
87 0 : "may double-vote. Never take over on both machines at once.\n" ));
88 0 : } else if( args->failover.takeover ) {
89 0 : FD_LOG_STDOUT(( "This asks the machine running the identity to hand it over. If that fails and\n"
90 0 : "the other machine shows no signs of life, neither in gossip for 15 seconds nor on\n"
91 0 : "the failover channel, this machine takes the identity anyway.\n"
92 0 : "WARNING: a takeover skips the checks on the other machine. A machine that is only\n"
93 0 : "cut off from this one may still sign votes, and both machines may double-vote.\n" ));
94 0 : }
95 0 : if( FD_UNLIKELY( !args->failover.yes && isatty( STDIN_FILENO ) ) ) {
96 0 : if( !args->failover.takeover ) {
97 0 : FD_LOG_STDOUT(( "This asks the remote validator running this identity to hand it to this machine. It keeps\n"
98 0 : "the identity if this machine is more than 64 slots behind its finality anchor.\n"
99 0 : "Otherwise, it gives up the identity before this validator adopts its history.\n"
100 0 : "If replay stalls, the handoff can fail with neither machine voting.\n"
101 0 : "Check that this machine is caught up. Type yes to continue: " ));
102 0 : } else {
103 0 : FD_LOG_STDOUT(( "Type yes to continue: " ));
104 0 : }
105 0 : char line[ 16 ] = {0};
106 0 : if( FD_UNLIKELY( !fgets( line, sizeof(line), stdin ) ) ) FD_LOG_ERR(( "no confirmation given" ));
107 0 : if( FD_UNLIKELY( strcmp( line, "yes\n" ) ) ) FD_LOG_ERR(( "not confirmed, nothing was done" ));
108 0 : }
109 0 : }
110 :
111 : /* Prints a step of the command as the failover tile takes it. */
112 : static void
113 0 : failover_print_step( char const * step ) {
114 0 : FD_LOG_NOTICE(( "%s", step ));
115 0 : }
116 :
117 : static uchar const * FD_FN_SENSITIVE
118 0 : failover_keypair_load( char const * path ) {
119 0 : if( FD_LIKELY( strcmp( path, "-" ) ) ) return fd_keyload_load( path, 0 );
120 0 : int fd = dup( STDIN_FILENO );
121 0 : if( FD_UNLIKELY( -1==fd ) ) FD_LOG_ERR(( "dup(stdin) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
122 0 : uchar * keypair = fd_keyload_alloc_protected_pages( 1UL, 2UL );
123 0 : FD_LOG_STDOUT(( "Reading identity keypair from stdin. Press Ctrl-D when done.\n" ));
124 0 : fd_keyload_read( fd, "stdin", keypair );
125 0 : return fd_keyload_mprotect_ro( keypair, 0 );
126 0 : }
127 :
128 : /* Sends req to the failover tile via adminctl and waits for the answer.
129 : Returns the adminctl result, along with the identity this machine will
130 : use afterwards in resp. */
131 : static ulong FD_FN_SENSITIVE
132 : failover_request( fd_adminctl_t * adminctl,
133 : fd_adminctl_failover_req_t const * req,
134 : uchar const * keypair,
135 0 : fd_adminctl_failover_resp_t * resp ) {
136 0 : void * payload = NULL;
137 0 : ulong payload_max = 0UL;
138 0 : ulong slot_idx = fd_adminctl_reserve( adminctl, &payload, &payload_max );
139 0 : if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) FD_LOG_ERR(( "all admin command slots are busy" ));
140 0 : ulong payload_sz = keypair ? sizeof(fd_adminctl_failover_prepare_t) : sizeof(*req);
141 0 : if( FD_UNLIKELY( payload_sz>payload_max ) ) FD_LOG_ERR(( "adminctl failover payload too large" ));
142 0 : fd_memcpy( payload, req, sizeof(*req) );
143 0 : if( keypair ) fd_memcpy( ((fd_adminctl_failover_prepare_t *)payload)->keypair, keypair, 64UL );
144 :
145 0 : fd_adminctl_publish( adminctl, slot_idx, FD_ADMINCTL_CMD_FAILOVER_PROMOTE, payload_sz );
146 0 : ulong resp_sz = 0UL;
147 0 : ulong result = fd_adminctl_wait_steps( adminctl, slot_idx, resp, sizeof(*resp), &resp_sz, failover_print_step );
148 0 : if( FD_UNLIKELY( resp_sz!=sizeof(*resp) || resp->version!=FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION ) ) fd_memset( resp, 0, sizeof(*resp) );
149 0 : return result;
150 0 : }
151 :
152 : /* Exit codes. */
153 :
154 0 : #define FAILOVER_EXIT_OTHER (1) /* any other failure, as FD_LOG_ERR exits */
155 0 : #define FAILOVER_EXIT_REFUSED (2) /* refused, this command changed no identity */
156 : #define FAILOVER_EXIT_UNKNOWN (3) /* the outcome is unknown */
157 0 : #define FAILOVER_EXIT_NOBODY (4) /* nobody runs the identity */
158 :
159 : struct failover_result {
160 : ulong result;
161 : char const * name;
162 : char const * meaning;
163 : int exit_code;
164 : int takeover;
165 : };
166 : typedef struct failover_result failover_result_t;
167 :
168 : static failover_result_t const failover_results[] = {
169 : { FD_FAILOVER_CONTROL_RESULT_BUSY, "busy", "another failover command is running on the local validator", FAILOVER_EXIT_REFUSED, 0 },
170 : { FD_FAILOVER_CONTROL_RESULT_IN_PROGRESS, "in_progress", "a handoff or identity switch is running on the local or the remote validator", FAILOVER_EXIT_REFUSED, 0 },
171 : { FD_FAILOVER_CONTROL_RESULT_NO_HOLDER_ADDRESS, "no_holder_address", "gossip did not show a validator running the identity", FAILOVER_EXIT_REFUSED, 1 },
172 : { FD_FAILOVER_CONTROL_RESULT_NOT_HOLDER, "not_holder", "the validator at the address does not run the identity", FAILOVER_EXIT_REFUSED, 1 },
173 : { FD_FAILOVER_CONTROL_RESULT_NO_PEER, "no_peer", "no validator running the identity answered at the address before the deadline", FAILOVER_EXIT_REFUSED, 1 },
174 : { FD_FAILOVER_CONTROL_RESULT_HANDED_OFF, "handed_off", "the local validator handed the identity off and the remote validator has not confirmed", FAILOVER_EXIT_REFUSED, 1 },
175 : { FD_FAILOVER_CONTROL_RESULT_NO_FINAL_HISTORY, "no_final_history", "the remote validator has no vote history for its last vote yet", FAILOVER_EXIT_REFUSED, 1 },
176 : { FD_FAILOVER_CONTROL_RESULT_REPLAY_BEHIND, "replay_behind", "the local validator's replay is too far behind the finality anchor of the vote history to adopt", FAILOVER_EXIT_REFUSED, 1 },
177 : { FD_FAILOVER_CONTROL_RESULT_HOLDS_OTHER, "holds_other", "the local validator runs a staked identity other than the one in the keypair", FAILOVER_EXIT_REFUSED, 0 },
178 : { FD_FAILOVER_CONTROL_RESULT_HOLDER_LIVE, "holder_live", "the remote validator may still be live", FAILOVER_EXIT_REFUSED, 0 },
179 : { FD_SET_IDENTITY_RESULT_KEYPAIR_MISMATCH, "keypair_mismatch", "the public key in the keypair file does not match its private key", FAILOVER_EXIT_REFUSED, 0 },
180 : { FD_SET_IDENTITY_RESULT_AUTHORIZED_VOTER, "authorized_voter", "the identity is an authorized voter on the local validator", FAILOVER_EXIT_REFUSED, 0 },
181 : { FD_FAILOVER_CONTROL_RESULT_TIMEOUT, "timeout", "the remote validator did not answer before the deadline", FAILOVER_EXIT_UNKNOWN, 1 },
182 : { FD_FAILOVER_CONTROL_RESULT_PEER_RESTARTED, "peer_restarted", "the remote validator restarted before it answered", FAILOVER_EXIT_UNKNOWN, 1 },
183 : { FD_FAILOVER_CONTROL_RESULT_HANDOFF_ABORTED, "handoff_aborted", "the remote validator aborted the handoff and may have given up the identity", FAILOVER_EXIT_UNKNOWN, 1 },
184 : { FD_FAILOVER_CONTROL_RESULT_PEER_HOLDS, "peer_holds", "another validator runs the identity", FAILOVER_EXIT_UNKNOWN, 1 },
185 : { FD_FAILOVER_CONTROL_RESULT_IDENTITY_CHANGED, "identity_changed", "set-identity changed the local validator's identity while the command ran", FAILOVER_EXIT_UNKNOWN, 0 },
186 : { FD_FAILOVER_CONTROL_RESULT_ADOPTION_FAILED, "adoption_failed", "the local validator did not adopt the vote history", FAILOVER_EXIT_NOBODY, 1 },
187 : { FD_FAILOVER_CONTROL_RESULT_SWITCH_FAILED, "switch_failed", "the local validator's identity switch failed", FAILOVER_EXIT_NOBODY, 1 },
188 : };
189 :
190 : static failover_result_t const *
191 0 : failover_result_find( ulong result ) {
192 0 : for( ulong i=0UL; i<sizeof(failover_results)/sizeof(failover_results[0]); i++ ) {
193 0 : if( failover_results[ i ].result==result ) return &failover_results[ i ];
194 0 : }
195 0 : return NULL;
196 0 : }
197 :
198 0 : #define FD_LOG_ERR_NOEXIT(a) do { \
199 0 : long _fd_log_msg_now = fd_log_wallclock(); \
200 0 : fd_log_private_1( 4, _fd_log_msg_now, __FILE__, __LINE__, __func__, \
201 0 : fd_log_private_0 a ); \
202 0 : } while(0)
203 :
204 : /* The command as the operator typed it. */
205 : static char const *
206 0 : failover_cmd_name( args_t const * args ) {
207 0 : return !args->failover.takeover ? "failover promote" : args->failover.force ? "failover takeover --force" : "failover takeover";
208 0 : }
209 :
210 : /* The exit code for a failed command. */
211 : static int
212 : failover_exit_code( ulong result,
213 0 : ulong handoff ) {
214 0 : int code = failover_result_find( result )->exit_code;
215 0 : if( handoff!=FD_ADMINCTL_RESULT_SUCCESS && handoff!=FD_FAILOVER_CONTROL_RESULT_HANDED_OFF && result!=FD_FAILOVER_CONTROL_RESULT_IDENTITY_CHANGED ) {
216 0 : code = failover_result_find( handoff )->exit_code;
217 0 : }
218 0 : return code;
219 0 : }
220 :
221 : static void __attribute__((noreturn))
222 : fail_result( char const * cmd,
223 : ulong result,
224 0 : ulong handoff ) {
225 0 : switch( result ) {
226 0 : case FD_ADMINCTL_RESULT_UNKNOWN_COMMAND:
227 0 : case FD_ADMINCTL_RESULT_UNSUPPORTED:
228 0 : case FD_ADMINCTL_RESULT_ABI_VERSION_MISMATCH:
229 0 : case FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH:
230 0 : FD_LOG_ERR(( "this command is incompatible with the running validator" ));
231 0 : default:
232 0 : break;
233 0 : }
234 0 : failover_result_t const * row = failover_result_find( result );
235 0 : if( FD_UNLIKELY( !row ) ) {
236 0 : FD_LOG_ERR_NOEXIT(( "`%s` failed with result %#lx, check both validators with `firedancer get-identity`", cmd, result ));
237 0 : exit( FAILOVER_EXIT_OTHER );
238 0 : }
239 0 : int code = failover_exit_code( result, handoff );
240 0 : char const * state = code==FAILOVER_EXIT_REFUSED ? "this command changed no identity" :
241 0 : code==FAILOVER_EXIT_NOBODY ? "nobody runs the identity" :
242 0 : "the outcome is unknown, check both validators with `firedancer get-identity`";
243 0 : FD_LOG_ERR_NOEXIT(( "`%s` failed (%s), %s, %s", cmd, row->name, row->meaning, state ));
244 0 : exit( code );
245 0 : }
246 :
247 : /* Run sanity checks before invoking failover. */
248 : static void FD_FN_SENSITIVE
249 : failover_preflight( fd_adminctl_t * adminctl,
250 : args_t const * args,
251 0 : uchar const * keypair ) {
252 0 : fd_adminctl_failover_req_t req = { .version=FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION, .cmd=FD_ADMINCTL_FAILOVER_CMD_PREFLIGHT };
253 0 : if( FD_UNLIKELY( args->failover.takeover ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER;
254 0 : if( FD_UNLIKELY( args->failover.force ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_FORCE;
255 0 : fd_sha512_t sha512[ 1 ];
256 0 : FD_TEST( fd_sha512_join( fd_sha512_new( sha512 ) ) );
257 0 : fd_ed25519_public_from_private( req.target, keypair, sha512 );
258 0 : fd_adminctl_failover_resp_t resp;
259 0 : ulong result = failover_request( adminctl, &req, NULL, &resp );
260 0 : if( FD_UNLIKELY( result!=FD_ADMINCTL_RESULT_SUCCESS ) ) fail_result( failover_cmd_name( args ), result, FD_ADMINCTL_RESULT_SUCCESS );
261 0 : }
262 :
263 : /* Asks the failover tile for the handoff and waits for its outcome. */
264 : static void
265 : failover_cmd_fn( args_t * args,
266 0 : config_t * config ) {
267 0 : fd_adminctl_t * adminctl = adminctl_client_attach( config, args->failover.name );
268 0 : char const * cmd = failover_cmd_name( args );
269 0 : uchar const * keypair = args->failover.keypair_path[ 0 ] ? failover_keypair_load( args->failover.keypair_path ) : NULL;
270 0 : if( keypair ) failover_preflight( adminctl, args, keypair );
271 0 : failover_confirm( args );
272 :
273 0 : fd_adminctl_failover_req_t req;
274 0 : fd_memset( &req, 0, sizeof(req) );
275 0 : req.version = FD_ADMINCTL_FAILOVER_PAYLOAD_VERSION;
276 0 : req.cmd = FD_ADMINCTL_FAILOVER_CMD_HANDOFF;
277 0 : if( FD_UNLIKELY( args->failover.yes ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_YES;
278 0 : if( FD_UNLIKELY( args->failover.takeover ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER;
279 0 : if( FD_UNLIKELY( args->failover.force ) ) req.flags |= FD_ADMINCTL_FAILOVER_FLAG_FORCE;
280 0 : req.addr = args->failover.addr;
281 0 : req.port = args->failover.port;
282 :
283 0 : char const * bold = fd_log_style_bold();
284 0 : char const * normal = fd_log_style_normal();
285 0 : if( args->failover.addr ) {
286 0 : FD_LOG_NOTICE(( "requesting handoff of the identity from peer `" FD_IP4_ADDR_FMT ":%hu`", FD_IP4_ADDR_FMT_ARGS( args->failover.addr ),
287 0 : (ushort)( args->failover.port ? args->failover.port : 8010 ) ));
288 0 : } else {
289 0 : FD_LOG_NOTICE(( "requesting handoff of the identity from the peer running it in gossip" ));
290 0 : }
291 :
292 0 : fd_adminctl_failover_resp_t resp;
293 0 : ulong result = failover_request( adminctl, &req, keypair, &resp );
294 0 : ulong handoff = FD_ADMINCTL_RESULT_SUCCESS;
295 :
296 0 : failover_result_t const * failed = failover_result_find( result );
297 0 : if( FD_UNLIKELY( failed && failed->takeover && args->failover.takeover ) ) {
298 0 : FD_LOG_WARNING(( "the handoff failed (%s), %s", failed->name, failed->meaning ));
299 0 : handoff = result;
300 0 : if( args->failover.force ) FD_LOG_NOTICE(( "taking the identity without the other machine, --force skips all checks" ));
301 0 : else FD_LOG_NOTICE(( "taking the identity if the other machine shows no signs of life" ));
302 :
303 0 : req.cmd = FD_ADMINCTL_FAILOVER_CMD_PROMOTE;
304 0 : req.flags = req.flags & (FD_ADMINCTL_FAILOVER_FLAG_YES|FD_ADMINCTL_FAILOVER_FLAG_TAKEOVER|FD_ADMINCTL_FAILOVER_FLAG_FORCE);
305 0 : req.addr = 0U;
306 0 : req.port = (ushort)0;
307 0 : if( keypair ) fd_memcpy( req.target, keypair+32UL, 32UL );
308 0 : result = failover_request( adminctl, &req, NULL, &resp );
309 0 : }
310 0 : if( keypair ) fd_keyload_unload( keypair, 0 );
311 :
312 0 : int already = result==FD_FAILOVER_CONTROL_RESULT_ALREADY_HOLDS;
313 0 : if( FD_UNLIKELY( result!=FD_ADMINCTL_RESULT_SUCCESS && !already ) ) fail_result( cmd, result, handoff );
314 0 : if( FD_UNLIKELY( !resp.version ) ) {
315 0 : FD_LOG_ERR_NOEXIT(( "`%s` returned an incompatible response", cmd ));
316 0 : exit( FAILOVER_EXIT_OTHER );
317 0 : }
318 0 : FD_BASE58_ENCODE_32_BYTES( resp.identity, identity_b58 );
319 0 : if( FD_UNLIKELY( already ) ) {
320 0 : FD_LOG_NOTICE(( "the local validator already runs the identity %s%s%s", bold, identity_b58, normal ));
321 0 : return;
322 0 : }
323 0 : if( resp.slot==ULONG_MAX ) FD_LOG_NOTICE(( "promotion success (identity now %s%s%s, no earlier vote)", bold, identity_b58, normal ));
324 0 : else FD_LOG_NOTICE(( "promotion success (identity now %s%s%s at slot %lu)", bold, identity_b58, normal, resp.slot ));
325 0 : }
326 :
327 : static void
328 0 : failover_args_help( fd_action_help_t * help ) {
329 0 : fd_action_help_arg( help, "promote", NULL, "Ask the machine running the identity to hand it over to this validator and\n"
330 0 : "wait for the result. For planned switches, the other machine must be up." );
331 0 : fd_action_help_arg( help, "takeover", NULL, "Like promote, but if the handoff fails and the machine running the identity\n"
332 0 : "shows no signs of life, take the identity without it, from an empty vote\n"
333 0 : "history. Use when that machine is down or unreachable." );
334 0 : fd_action_help_arg( help, "[<keypair>]", NULL, "Path to the keypair of the identity to take, in the standard Solana keypair\n"
335 0 : "file format (the 64-byte JSON array), or `-` to read it from stdin. Defaults\n"
336 0 : "to [paths.identity_key] or the last keypair given to `failover` or\n"
337 0 : "`set-identity`." );
338 0 : fd_action_help_arg( help, "--name", "<name>", "Name of the validator instance to attach to, if more than one is\n"
339 0 : "running on this host" );
340 0 : fd_action_help_arg( help, "--yes", NULL, "Bypass the interactive confirmation prompt." );
341 0 : fd_action_help_arg( help, "--force", NULL, "takeover only. Take the identity even if the machine running it still shows\n"
342 0 : "signs of life, skipping every check. Only use when that machine cannot sign,\n"
343 0 : "or both machines may double-vote." );
344 0 : fd_action_help_arg( help, "--address", "<address>", "The target machine's IPv4 address or host name" );
345 0 : fd_action_help_arg( help, "--port", "<port>", "The target machine's failover port, 8010 by default" );
346 0 : }
347 :
348 : static void
349 : failover_promote_perm( args_t * args,
350 : fd_cap_chk_t * chk,
351 0 : config_t const * config FD_PARAM_UNUSED ) {
352 0 : if( args->failover.keypair_path[ 0 ] ) {
353 0 : fd_cap_chk_raise_rlimit( chk, failover_cmd_name( args ), RLIMIT_MEMLOCK, 5UL*FD_SHMEM_NORMAL_PAGE_SZ,
354 0 : "lock the target keypair in memory" );
355 0 : }
356 0 : }
357 :
358 : action_t fd_action_failover = {
359 : .name = "failover",
360 : .args = failover_args,
361 : .fn = failover_cmd_fn,
362 : .require_config = 0,
363 : .no_topo = 1,
364 : .perm = failover_promote_perm,
365 : .description = "Take the identity that another validator runs",
366 : .detail = "Hands off an identity between validators. Each boots under its own\n"
367 : "[paths.identity_key]. On the machine that should take over, pass the keypair\n"
368 : "of the target identity.\n"
369 : "A validator with [failover.boot_to_standby] boots as a standby under a junk key\n"
370 : "generated in memory and loads the keypair in its [paths.identity_key] as the\n"
371 : "identity to take over, so `failover` there reads the keypair from the\n"
372 : "configuration instead of the command line.\n"
373 : "\n"
374 : "`failover promote` asks the machine running the identity to hand it over.\n"
375 : "`failover takeover` does the same, and if that fails and the other machine shows\n"
376 : "no signs of life, takes the identity without handover. With --force, it takes\n"
377 : "the identity even from a machine that may be live.\n"
378 : "This command asks for confirmation unless --yes is given.\n"
379 : "It waits for the outcome and exits 0 once this machine runs the identity,\n"
380 : "and non-zero with the reason when it does not. For checking status,\n"
381 : "`get-identity` prints the key a machine runs.\n"
382 : "\n"
383 : "This command does not start a validator. It attaches to one that is already\n"
384 : "running. If multiple validators are running, pass --name to select one.\n",
385 : .usage = "failover <promote|takeover> [<keypair>] [--name <name>] [--yes] [--force] [--address <address>] [--port <port>]",
386 : .args_help = failover_args_help,
387 : };
|