Line data Source code
1 : #include "../../disco/topo/fd_topo.h"
2 : #include "../../disco/topo/fd_dns_resolve.h"
3 : #include "../../disco/keyguard/fd_keyload.h"
4 : #include "../../disco/keyguard/fd_keyguard.h"
5 : #include "../../disco/keyguard/fd_keyguard_client.h"
6 : #include "../../flamenco/gossip/fd_gossip_message.h"
7 : #include "../../ballet/base58/fd_base58.h"
8 : #include "../../ballet/hex/fd_hex.h"
9 : #include "../../util/fd_version.h"
10 : #include "../../util/net/fd_ip4.h"
11 : #include "../votor/fd_votor_tile.h"
12 :
13 : #include "fd_failover_bus.h"
14 : #include "fd_failover_channel.h"
15 :
16 : #include <string.h>
17 : #include <sys/socket.h>
18 : #include <netinet/in.h> /* IPPROTO_TCP */
19 : #include <netinet/tcp.h> /* TCP_NODELAY */
20 : #include <linux/futex.h> /* FUTEX_WAIT_BITSET */
21 :
22 : #include "generated/fd_failover_tile_seccomp.h"
23 :
24 : /* The failov tile owns the sockets to the other failover machine, keeps
25 : host networking for them and holds no private keys. The sign tile
26 : signs the admission proofs and our TLS handshakes with the failover
27 : identity, and the admin tile does the identity switch and stays off
28 : the network. */
29 :
30 0 : #define FD_FAILOVER_TILE_POLL_NANOS (50000L) /* 50us */
31 0 : #define FD_FAILOVER_TILE_PARK_NANOS (1000000L) /* 1ms */
32 :
33 0 : #define FD_FAILOVER_DEADLINE_SLOTS (64UL)
34 0 : #define FD_FAILOVER_DEADLINE_NANOS ((long)FD_FAILOVER_DEADLINE_SLOTS*(long)FD_FAILOVER_NANOS_PER_SECOND)
35 :
36 0 : #define FD_FAILOVER_GOSSIP_FRESH_NANOS (15000000000L)
37 : #define FD_FAILOVER_REQUEST_RETRIES (3UL)
38 0 : #define FD_FAILOVER_FORCE_WAIT_NANOS FD_FAILOVER_GOSSIP_FRESH_NANOS
39 0 : #define FD_FAILOVER_STEP_CNT (8UL)
40 :
41 : /* apply_control's result for a pending command */
42 0 : #define FD_FAILOVER_CMD_PENDING (ULONG_MAX)
43 :
44 : /* The current action/state of the controller */
45 0 : #define FD_FAILOVER_ACTION_IDLE (0UL)
46 0 : #define FD_FAILOVER_ACTION_DEMOTE_SWITCH (1UL)
47 0 : #define FD_FAILOVER_ACTION_DEMOTE_WAIT_ACK (2UL)
48 0 : #define FD_FAILOVER_ACTION_PROMOTE_WAIT_REPLAY (3UL)
49 0 : #define FD_FAILOVER_ACTION_PROMOTE_WAIT_ADOPT (4UL)
50 0 : #define FD_FAILOVER_ACTION_PROMOTE_SWITCH (5UL)
51 0 : #define FD_FAILOVER_ACTION_HANDOFF_WAIT_PEER (6UL)
52 0 : #define FD_FAILOVER_ACTION_HANDOFF_WAIT_RESULT (7UL)
53 0 : #define FD_FAILOVER_ACTION_DEMOTE_DRAIN (8UL)
54 0 : #define FD_FAILOVER_ACTION_CNT (9UL)
55 :
56 : /* Adopted vote history's source */
57 0 : #define FD_FAILOVER_SOURCE_PEER (0UL)
58 0 : #define FD_FAILOVER_SOURCE_EMPTY (1UL)
59 :
60 : /* Handoff result */
61 : #define FD_FAILOVER_HANDOFF_NONE (0UL)
62 0 : #define FD_FAILOVER_HANDOFF_PENDING (1UL)
63 0 : #define FD_FAILOVER_HANDOFF_TAKEN (2UL)
64 0 : #define FD_FAILOVER_HANDOFF_DECLINED (3UL)
65 0 : #define FD_FAILOVER_HANDOFF_RESTARTED (4UL)
66 0 : #define FD_FAILOVER_HANDOFF_CANCELLED (5UL)
67 0 : #define FD_FAILOVER_HANDOFF_NOT_HOLDER (6UL)
68 0 : #define FD_FAILOVER_HANDOFF_CNT (7UL)
69 :
70 : /* Which key we will switch to, CNT if not switching */
71 : #define FD_FAILOVER_SWITCH_KEY_JUNK (0UL)
72 0 : #define FD_FAILOVER_SWITCH_KEY_STAKED (1UL)
73 0 : #define FD_FAILOVER_SWITCH_KEY_CNT (2UL)
74 :
75 : /* The status of our DEMOTED to the handoff target */
76 0 : #define FD_FAILOVER_DELIVERY_NONE (0UL)
77 0 : #define FD_FAILOVER_DELIVERY_PENDING (1UL)
78 0 : #define FD_FAILOVER_DELIVERY_SENT (2UL)
79 :
80 : /* Our last response to a DEMOTED */
81 : #define FD_FAILOVER_REPLY_NONE (0UL)
82 0 : #define FD_FAILOVER_REPLY_KEPT (1UL)
83 0 : #define FD_FAILOVER_REPLY_PENDING (2UL)
84 :
85 : /* A serialized Alpenglow vote history. */
86 : struct fd_failover_history {
87 : int valid; /* holds a history */
88 : ulong tip; /* slot of its last vote */
89 : ulong sz; /* bytes used in state */
90 : uchar state[ FD_FAILOVER_STATE_MAX ]; /* the serialized history */
91 : };
92 :
93 : typedef struct fd_failover_history fd_failover_history_t;
94 :
95 : /* A peer is a boot of the other machine. Both authenticate as the
96 : identity they hand off, so only the boot id tells them apart. */
97 : struct fd_failover_peer {
98 : ulong boot_id; /* the peer's boot */
99 : };
100 :
101 : typedef struct fd_failover_peer fd_failover_peer_t;
102 :
103 : static inline int
104 : is_same_peer( fd_failover_peer_t const * bound,
105 0 : ulong boot_id ) {
106 0 : return bound->boot_id==boot_id;
107 0 : }
108 :
109 : struct fd_failover_session {
110 : ulong state; /* last seen session state */
111 : ulong generation; /* catches a replacement paired during one poll */
112 : ulong peer_boot_id; /* boot of the peer on this session */
113 : int close_after_send; /* hang up once the queued message is sent */
114 : };
115 :
116 : typedef struct fd_failover_session fd_failover_session_t;
117 :
118 : /* A handoff request to take over another machine for failover. */
119 : struct fd_failover_request {
120 : ulong id; /* zero when none is open */
121 : fd_failover_peer_t peer; /* the boot we authenticated for it, which runs the identity */
122 : uint addr; /* the address we dial, a resume may replace it */
123 : long until; /* when the request pauses, zero while paused */
124 : int sent; /* sent on this session */
125 : int peer_restarted; /* paused because the authenticated member or boot changed */
126 : ulong send_cnt; /* how many times we sent it */
127 : int quick; /* use quicker timeouts for failover */
128 : };
129 :
130 : typedef struct fd_failover_request fd_failover_request_t;
131 :
132 : /* A demotion. */
133 : struct fd_failover_handoff {
134 : ulong id; /* id of our latest demotion */
135 : fd_failover_peer_t target; /* peer boot it is for */
136 : ulong result; /* FD_FAILOVER_HANDOFF_* of the latest DEMOTED */
137 : ulong delivery; /* FD_FAILOVER_DELIVERY_* */
138 : ulong payload_sz; /* bytes used in payload */
139 : uchar payload[ FD_FAILOVER_DEMOTED_PAYLOAD_MAX ]; /* our DEMOTED, resent on a new session */
140 : };
141 :
142 : typedef struct fd_failover_handoff fd_failover_handoff_t;
143 :
144 : /* A promotion in progress. */
145 : struct fd_failover_promotion {
146 : char label[ 48 ]; /* handoff ID or operator promotion, fixed for every step */
147 : ulong source; /* FD_FAILOVER_SOURCE_* we adopt from */
148 : int from_peer; /* the peer's DEMOTED requested it, we send the response */
149 : fd_failover_peer_t peer; /* that DEMOTED's member boot, it gets our response */
150 : ulong handoff_id; /* and its handoff id */
151 : ulong floor; /* coverage floor when it started */
152 : int force; /* skip peer checks and accept incomplete or empty history */
153 : int soft; /* demote ourselves if a peer running the identity authenticates */
154 : int holder_seen; /* a peer running the identity authenticated after promotion started */
155 : int gave_up; /* the identity was already given up */
156 : fd_failover_history_t adopt; /* history or the bound sent to votor */
157 : ulong adopt_id; /* id of the adoption we asked the votor for */
158 : int retry; /* ask the votor again once replay passes retry_slot */
159 : ulong retry_slot; /* replay slot that retry waits for */
160 : };
161 :
162 : typedef struct fd_failover_promotion fd_failover_promotion_t;
163 :
164 : /* Our reply to a DEMOTED. */
165 : struct fd_failover_reply {
166 : ulong state; /* FD_FAILOVER_REPLY_* */
167 : fd_failover_peer_t peer; /* the member boot it goes to */
168 : ulong handoff_id; /* the handoff it answers */
169 : ushort type; /* PROMOTE_ACK or PROMOTE_REJECTED */
170 : uchar reason; /* for a rejected reply */
171 : };
172 :
173 : typedef struct fd_failover_reply fd_failover_reply_t;
174 :
175 : struct fd_failover_tx {
176 : ushort type; /* FD_FAILOVER_MSG_* */
177 : ushort sz; /* bytes used in payload */
178 : int valid; /* a message is waiting */
179 : fd_failover_peer_t to; /* the member boot it goes to */
180 : uchar payload[ FD_FAILOVER_DEMOTED_PAYLOAD_MAX ]; /* the encoded message */
181 : };
182 :
183 : typedef struct fd_failover_tx fd_failover_tx_t;
184 :
185 : /* State of the identity switch during failover. */
186 : struct fd_failover_id_switch {
187 : ulong request_id; /* nonce of our latest switch request */
188 : ulong pending_key; /* FD_FAILOVER_SWITCH_KEY_*, or CNT when idle */
189 : fd_failover_switch_resp_t result; /* the committed response */
190 : fd_failover_switch_resp_t response; /* uncommitted input frame */
191 : ulong response_nonce; /* nonce of the frame being consumed */
192 : int fresh; /* a response frame arrived and is not handled yet */
193 : int overdue; /* the switch in flight passed its deadline */
194 : int discard; /* set-identity ran while it was in flight, discard */
195 : ulong epoch; /* failover epoch */
196 : };
197 :
198 : typedef struct fd_failover_id_switch fd_failover_id_switch_t;
199 :
200 : struct fd_failover_tile_ctx {
201 : /* Networking */
202 : fd_keyguard_client_t keyguard_client[ 1 ]; /* signs admission proofs and TLS */
203 : fd_failover_hello_t hello; /* the pre-built HELLO we will send */
204 : uchar installed[ 32 ]; /* the identity this machine signs with now */
205 : uchar spare_pubkey[ 32 ]; /* the junk key generated at boot, a handoff installs it */
206 : uint cmd_addr; /* failover address, 0 if from gossip */
207 : ushort cmd_port; /* failover port, 0 if from gossip */
208 : fd_failover_channel_t * channel; /* the TLS channel to the peer */
209 : fd_failover_session_t session; /* the session we last saw on the channel */
210 : long next_wake; /* next socket poll, the park deadline */
211 : long target_since; /* our clock since we started watching gossip for the target */
212 :
213 : fd_failover_request_t request; /* our handoff request */
214 :
215 : ulong peer_floor;
216 : ulong own_floor;
217 : int own_voted;
218 : int installed_ranked;
219 :
220 : /* Gossip */
221 : ulong gossip_in_idx;
222 : fd_wksp_t * gossip_in_mem;
223 : ulong gossip_in_chunk0;
224 : ulong gossip_in_wmark;
225 : ulong gossip_in_mtu;
226 :
227 : fd_ip4_port_t own_gossip; /* our own gossip socket */
228 : uchar gossip_origin[ 32 ]; /* copied in during_frag */
229 : fd_ip4_port_t gossip_socket; /* copied in during_frag, zero for a remove or IPv6 */
230 : uint staked_addr; /* from a staked contact info that is not ours, 0 none */
231 : long staked_seen_at; /* our clock when that one arrived, 0 never */
232 : long holder_seen_at; /* our clock when a machine running the identity was last heard, 0 never */
233 :
234 : /* Controller */
235 : ulong action; /* FD_FAILOVER_ACTION_* */
236 : int stuck; /* a transition failed or is overdue */
237 : int stuck_logged; /* the stuck value we last logged */
238 : ulong deadline_slot; /* replay slot past which the current state is overdue, SLOT_NULL before replay started */
239 : long deadline_at; /* our clock past which it is overdue */
240 :
241 : /* Hand-offs */
242 : ulong handoff_base; /* random base for our handoff ids */
243 : ulong handoff_cnt; /* handoff ids issued since boot */
244 : fd_failover_handoff_t handoff; /* our demotion */
245 :
246 : fd_failover_history_t peer_history; /* stored peer history, from a DEMOTED */
247 : fd_failover_promotion_t promotion; /* the promotion in progress */
248 : fd_failover_reply_t reply; /* our last response to a DEMOTED */
249 : fd_failover_tx_t tx; /* the control message waiting to go out */
250 :
251 : /* The admin tile forwards operator commands over the bus and waits for one response frame per request. */
252 : ulong admin_in_idx;
253 : fd_wksp_t * admin_in_mem;
254 : ulong admin_in_chunk0;
255 : ulong admin_in_wmark;
256 : ulong admin_out_idx;
257 : fd_wksp_t * admin_out_mem;
258 : ulong admin_out_chunk0;
259 : ulong admin_out_wmark;
260 : ulong admin_out_chunk;
261 :
262 : fd_failover_bus_msg_t bus_req; /* the operator command copied in during_frag */
263 : int bus_req_fresh; /* bus_req arrived and is not handled yet */
264 : fd_failover_operator_t bus_operator; /* copied in during_frag */
265 :
266 : ulong cmd_nonce; /* nonce of the command, 0 none */
267 : ulong cmd_result; /* cmd outcome once cmd_done */
268 : int cmd_done; /* cmd is done after_credit publishes it */
269 : ulong cmd_step_head; /* steps logged */
270 : ulong cmd_step_tail; /* steps sent to the admin tile or dropped */
271 : char cmd_step[ FD_FAILOVER_STEP_CNT ][ FD_FAILOVER_BUS_STEP_MAX ]; /* the last steps, sent while the command waits */
272 :
273 : fd_failover_id_switch_t id_switch; /* the identity switch we asked the admin tile for */
274 :
275 : /* Votor history adoption */
276 : ulong adopt_out_idx;
277 : fd_wksp_t * adopt_out_mem;
278 : ulong adopt_out_chunk0;
279 : ulong adopt_out_wmark;
280 : ulong adopt_out_chunk;
281 : ulong adopt_request_id; /* id of our latest adoption request */
282 : ulong adopt_in_idx;
283 : fd_wksp_t * adopt_in_mem;
284 : ulong adopt_in_chunk0;
285 : ulong adopt_in_wmark;
286 :
287 : fd_votor_adopt_result_t adopt_result;
288 : ulong adopt_result_id;
289 : int adopt_result_fresh; /* adopt_result arrived and is not handled yet */
290 :
291 : /* Vote history input. */
292 : ulong hist_in_idx;
293 : fd_wksp_t * hist_in_mem;
294 : ulong hist_in_chunk0;
295 : ulong hist_in_wmark;
296 :
297 : int hist_fresh; /* a history frame arrived and is not handled yet */
298 : ulong hist_seen_seq; /* seq of the last votor_hist frag taken in, ULONG_MAX before any */
299 :
300 : /* Votor history and adoption bounds. */
301 : fd_votor_hist_msg_t hist; /* the votor_hist frame copied in during_frag */
302 : ulong adopt_anchor; /* finality anchor of the history being adopted, SLOT_NULL when none */
303 : ulong empty_vote_after; /* an empty history votes only past this slot, SLOT_NULL until known */
304 : int hist_warned; /* we warned of a bad frame and no good one came since */
305 :
306 : ulong replay_slot; /* latest replayed slot */
307 : ulong root_slot; /* latest root */
308 : ulong last_vote_slot; /* our latest vote */
309 :
310 : fd_failover_history_t current_history; /* history of our latest vote */
311 :
312 : uchar rx[ FD_FAILOVER_PAYLOAD_MAX ]; /* the control message being decoded */
313 : };
314 :
315 : typedef struct fd_failover_tile_ctx fd_failover_tile_ctx_t;
316 :
317 : /* Whether this machine currently runs the staked identity it hands off. */
318 : static inline int
319 0 : holds_identity( fd_failover_tile_ctx_t const * ctx ) {
320 0 : return fd_memeq( ctx->installed, ctx->hello.staked_pubkey, 32UL );
321 0 : }
322 :
323 : FD_FN_CONST static inline ulong
324 0 : scratch_align( void ) {
325 0 : return fd_ulong_max( alignof(fd_failover_tile_ctx_t), fd_failover_channel_align() );
326 0 : }
327 :
328 : FD_FN_PURE static inline ulong
329 0 : scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
330 0 : ulong l = FD_LAYOUT_INIT;
331 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_failover_tile_ctx_t), sizeof(fd_failover_tile_ctx_t) );
332 0 : l = FD_LAYOUT_APPEND( l, fd_failover_channel_align(), fd_failover_channel_footprint() );
333 0 : return FD_LAYOUT_FINI( l, scratch_align() );
334 0 : }
335 :
336 : /* TLS CertificateVerify */
337 : static void
338 : sign_ed25519( void * signer_ctx,
339 : uchar sig[ static FD_ED25519_SIG_SZ ],
340 0 : uchar const msg[ static FD_TLS_CV_SIGN_SZ ] ) {
341 0 : fd_failover_tile_ctx_t * ctx = signer_ctx;
342 0 : fd_keyguard_client_sign( ctx->keyguard_client, sig, msg, FD_TLS_CV_SIGN_SZ, FD_KEYGUARD_SIGN_TYPE_ED25519 );
343 0 : }
344 :
345 : /* Pre-admission proof */
346 : static void
347 : sign_proof( void * signer_ctx,
348 : uchar sig[ 64 ],
349 0 : uchar const msg[ FD_KEYGUARD_FAILOVER_PROOF_SZ ] ) {
350 0 : fd_failover_tile_ctx_t * ctx = signer_ctx;
351 0 : fd_keyguard_client_sign( ctx->keyguard_client, sig, msg, FD_KEYGUARD_FAILOVER_PROOF_SZ, FD_KEYGUARD_SIGN_TYPE_ED25519 );
352 0 : }
353 :
354 : static void
355 : privileged_init( fd_topo_t const * topo,
356 0 : fd_topo_tile_t const * tile ) {
357 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
358 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
359 0 : fd_failover_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_failover_tile_ctx_t), sizeof(fd_failover_tile_ctx_t) );
360 0 : void * channel_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_failover_channel_align(), fd_failover_channel_footprint() );
361 : /* Start idle with no handoff or promotion in progress. */
362 0 : fd_memset( ctx, 0, sizeof(fd_failover_tile_ctx_t) );
363 : /* No admin switch is pending at boot. */
364 0 : ctx->id_switch.pending_key = FD_FAILOVER_SWITCH_KEY_CNT;
365 0 : ctx->deadline_slot = FD_FAILOVER_SLOT_NULL;
366 0 : ctx->peer_floor = FD_FAILOVER_SLOT_NULL;
367 0 : ctx->own_floor = FD_FAILOVER_SLOT_NULL;
368 0 : ctx->replay_slot = FD_FAILOVER_SLOT_NULL;
369 0 : ctx->root_slot = FD_FAILOVER_SLOT_NULL;
370 0 : ctx->last_vote_slot = FD_FAILOVER_SLOT_NULL;
371 0 : ctx->hist_seen_seq = ULONG_MAX;
372 0 : FD_TEST( fd_rng_secure( &ctx->handoff_base, sizeof(ulong) ) );
373 :
374 : /* The junk key generated at boot is the spare a handoff installs.
375 : The configured identity is the key we hand off. */
376 0 : uchar const * spare_pubkey = fd_keyload_load( FD_KEYLOAD_FAILOVER_JUNK, 1 );
377 0 : fd_memcpy( ctx->spare_pubkey, spare_pubkey, 32UL );
378 0 : fd_keyload_unload( spare_pubkey, 1 );
379 :
380 0 : uchar const * target_pubkey = fd_keyload_load( tile->failov.target_key_path, 1 );
381 0 : fd_memcpy( ctx->hello.staked_pubkey, target_pubkey, 32UL );
382 0 : fd_keyload_unload( target_pubkey, 1 );
383 :
384 0 : uchar const * identity_pubkey = fd_keyload_load( tile->failov.identity_key_path, 1 );
385 0 : fd_memcpy( ctx->installed, identity_pubkey, 32UL );
386 0 : fd_keyload_unload( identity_pubkey, 1 );
387 :
388 0 : if( FD_UNLIKELY( fd_memeq( ctx->spare_pubkey, ctx->hello.staked_pubkey, 32UL ) ) ) {
389 0 : FD_LOG_ERR(( "the failover junk key must differ from [paths.identity_key]" ));
390 0 : }
391 :
392 0 : ctx->adopt_anchor = FD_FAILOVER_SLOT_NULL;
393 0 : ctx->empty_vote_after = FD_FAILOVER_SLOT_NULL;
394 :
395 0 : ctx->hello.version = (ushort)FD_FAILOVER_VERSION;
396 0 : ctx->hello.holds = (uchar)holds_identity( ctx );
397 0 : ctx->hello.mode = (uchar)FD_FAILOVER_MODE_ALPENGLOW;
398 0 : while( FD_UNLIKELY( !ctx->hello.boot_id ) ) FD_TEST( fd_rng_secure( &ctx->hello.boot_id, sizeof(ulong) ) );
399 :
400 : /* The git commit field with the hash bytes. */
401 0 : if( FD_LIKELY( strlen( fd_commit_ref_cstr )==2UL*sizeof(ctx->hello.commit) &&
402 0 : fd_hex_decode( ctx->hello.commit, fd_commit_ref_cstr, sizeof(ctx->hello.commit) )!=sizeof(ctx->hello.commit) ) ) {
403 0 : fd_memset( ctx->hello.commit, 0, sizeof(ctx->hello.commit) );
404 0 : }
405 :
406 0 : ctx->own_gossip = tile->failov.gossip_addr;
407 0 : if( FD_UNLIKELY( tile->failov.gossip_host[ 0 ]!='\0' ) ) {
408 0 : if( FD_UNLIKELY( !fd_dns_resolve_address( tile->failov.gossip_host, &ctx->own_gossip.addr ) ) ) {
409 0 : FD_LOG_ERR(( "could not resolve [gossip.host] `%s`", tile->failov.gossip_host ));
410 0 : }
411 0 : }
412 :
413 0 : ctx->channel = fd_failover_channel_join( fd_failover_channel_new( channel_mem ) );
414 0 : FD_TEST( ctx->channel );
415 0 : fd_tls_sign_t signer = { .ctx=ctx, .sign_fn=sign_ed25519 };
416 0 : if( FD_UNLIKELY( fd_failover_channel_set_identity( ctx->channel, signer, (fd_failover_proof_signer_t){ .ctx=ctx, .sign_fn=sign_proof }, &ctx->hello ) ) ) {
417 0 : FD_LOG_ERR(( "failover TLS setup failed" ));
418 0 : }
419 :
420 0 : if( FD_LIKELY( tile->failov.listen ) ) fd_failover_channel_init_listener( ctx->channel, tile->failov.listen_addr, tile->failov.port );
421 0 : ctx->session.state = fd_failover_channel_state( ctx->channel );
422 :
423 0 : char const * role = holds_identity( ctx ) ? "primary" : "standby";
424 0 : if( FD_LIKELY( tile->failov.listen ) ) {
425 0 : FD_LOG_NOTICE(( "failover booted %s server listening on " FD_IP4_ADDR_FMT ":%u", role, FD_IP4_ADDR_FMT_ARGS( tile->failov.listen_addr ), (uint)tile->failov.port ));
426 0 : } else {
427 0 : FD_LOG_NOTICE(( "failover booted %s server not listening, [failover.enabled] is false", role ));
428 0 : }
429 :
430 0 : char junk_b58 [ FD_BASE58_ENCODED_32_SZ ];
431 0 : char staked_b58[ FD_BASE58_ENCODED_32_SZ ];
432 0 : fd_base58_encode_32( ctx->spare_pubkey, NULL, junk_b58 );
433 0 : fd_base58_encode_32( ctx->hello.staked_pubkey, NULL, staked_b58 );
434 0 : FD_LOG_INFO(( "failover identity `%s`, junk key `%s`", staked_b58, junk_b58 ));
435 :
436 0 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, scratch_align() )==(ulong)scratch+scratch_footprint( tile ) );
437 0 : }
438 :
439 : static void
440 : unprivileged_init( fd_topo_t const * topo,
441 0 : fd_topo_tile_t const * tile ) {
442 0 : fd_failover_tile_ctx_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
443 :
444 0 : ctx->gossip_in_idx = ULONG_MAX;
445 0 : ctx->hist_in_idx = ULONG_MAX;
446 0 : ctx->admin_in_idx = ULONG_MAX;
447 0 : ctx->adopt_in_idx = ULONG_MAX;
448 0 : ctx->target_since = fd_failover_clock();
449 0 : ctx->adopt_out_idx = fd_topo_find_tile_out_link( topo, tile, "failov_votor", 0UL );
450 0 : if( FD_LIKELY( ctx->adopt_out_idx!=ULONG_MAX ) ) {
451 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ ctx->adopt_out_idx ] ];
452 0 : ctx->adopt_out_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
453 0 : ctx->adopt_out_chunk0 = fd_dcache_compact_chunk0( ctx->adopt_out_mem, link->dcache );
454 0 : ctx->adopt_out_wmark = fd_dcache_compact_wmark ( ctx->adopt_out_mem, link->dcache, link->mtu );
455 0 : ctx->adopt_out_chunk = ctx->adopt_out_chunk0;
456 0 : }
457 0 : ctx->admin_out_idx = fd_topo_find_tile_out_link( topo, tile, "failov_admin", 0UL );
458 0 : if( FD_LIKELY( ctx->admin_out_idx!=ULONG_MAX ) ) {
459 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ ctx->admin_out_idx ] ];
460 0 : ctx->admin_out_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
461 0 : ctx->admin_out_chunk0 = fd_dcache_compact_chunk0( ctx->admin_out_mem, link->dcache );
462 0 : ctx->admin_out_wmark = fd_dcache_compact_wmark ( ctx->admin_out_mem, link->dcache, link->mtu );
463 0 : ctx->admin_out_chunk = ctx->admin_out_chunk0;
464 0 : }
465 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
466 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
467 0 : if( FD_LIKELY( !strcmp( link->name, "gossip_ciseen" ) ) ) {
468 0 : ctx->gossip_in_idx = i;
469 0 : ctx->gossip_in_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
470 0 : ctx->gossip_in_chunk0 = fd_dcache_compact_chunk0( ctx->gossip_in_mem, link->dcache );
471 0 : ctx->gossip_in_wmark = fd_dcache_compact_wmark ( ctx->gossip_in_mem, link->dcache, link->mtu );
472 0 : ctx->gossip_in_mtu = link->mtu;
473 0 : continue;
474 0 : }
475 0 : if( FD_LIKELY( !strcmp( link->name, "votor_failov" ) ) ) {
476 0 : ctx->adopt_in_idx = i;
477 0 : ctx->adopt_in_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
478 0 : ctx->adopt_in_chunk0 = fd_dcache_compact_chunk0( ctx->adopt_in_mem, link->dcache );
479 0 : ctx->adopt_in_wmark = fd_dcache_compact_wmark ( ctx->adopt_in_mem, link->dcache, link->mtu );
480 0 : continue;
481 0 : }
482 0 : if( FD_LIKELY( !strcmp( link->name, "admin_failov" ) ) ) {
483 0 : ctx->admin_in_idx = i;
484 0 : ctx->admin_in_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
485 0 : ctx->admin_in_chunk0 = fd_dcache_compact_chunk0( ctx->admin_in_mem, link->dcache );
486 0 : ctx->admin_in_wmark = fd_dcache_compact_wmark ( ctx->admin_in_mem, link->dcache, link->mtu );
487 0 : continue;
488 0 : }
489 0 : if( FD_LIKELY( !strcmp( link->name, "votor_hist" ) ) ) {
490 0 : ctx->hist_in_idx = i;
491 0 : ctx->hist_in_mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
492 0 : ctx->hist_in_chunk0 = fd_dcache_compact_chunk0( ctx->hist_in_mem, link->dcache );
493 0 : ctx->hist_in_wmark = fd_dcache_compact_wmark ( ctx->hist_in_mem, link->dcache, link->mtu );
494 0 : continue;
495 0 : }
496 : /* The keyguard client reads sign_failov itself. */
497 0 : if( FD_LIKELY( !strcmp( link->name, "sign_failov" ) ) ) continue;
498 0 : FD_LOG_ERR(( "unexpected input link name %s", link->name ));
499 0 : }
500 :
501 0 : ulong sign_in_idx = fd_topo_find_tile_in_link ( topo, tile, "sign_failov", tile->kind_id );
502 0 : ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "failov_sign", tile->kind_id );
503 0 : FD_TEST( sign_in_idx!=ULONG_MAX && sign_out_idx!=ULONG_MAX );
504 0 : fd_topo_link_t const * sign_in = &topo->links[ tile->in_link_id [ sign_in_idx ] ];
505 0 : fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ];
506 :
507 0 : fd_sleep_t * sleep = NULL;
508 0 : if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) {
509 0 : sleep = fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) );
510 0 : FD_TEST( sleep );
511 0 : }
512 :
513 0 : if( FD_UNLIKELY( !fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client,
514 0 : sign_out->mcache,
515 0 : sign_out->dcache,
516 0 : sign_in->mcache,
517 0 : sign_in->dcache,
518 0 : sign_out->mtu,
519 0 : sign_in->mtu,
520 0 : sleep,
521 0 : sign_out->id,
522 0 : fd_topo_find_link_consumer( topo, sign_out ) ) ) ) ) {
523 0 : FD_LOG_ERR(( "failed to construct keyguard" ));
524 0 : }
525 0 : }
526 :
527 : #include "fd_failover_controller.c"
528 :
529 : static inline int
530 : before_frag( fd_failover_tile_ctx_t * ctx,
531 : ulong in_idx,
532 : ulong seq FD_PARAM_UNUSED,
533 0 : ulong sig ) {
534 0 : if( FD_LIKELY( in_idx==ctx->gossip_in_idx ) ) {
535 0 : return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
536 0 : }
537 0 : if( FD_LIKELY( in_idx==ctx->hist_in_idx ) ) {
538 0 : return 0;
539 0 : }
540 0 : if( FD_UNLIKELY( in_idx==ctx->admin_in_idx ) ) {
541 : /* Commands, switch responses and set-identity. */
542 0 : return sig!=FD_FAILOVER_BUS_REQUEST && sig!=FD_FAILOVER_BUS_SWITCH_RESP && sig!=FD_FAILOVER_BUS_OPERATOR_ACTION;
543 0 : }
544 0 : return 0;
545 0 : }
546 :
547 : static void
548 : during_frag( fd_failover_tile_ctx_t * ctx,
549 : ulong in_idx,
550 : ulong seq FD_PARAM_UNUSED,
551 : ulong sig,
552 : ulong chunk,
553 : ulong sz,
554 0 : ulong ctl FD_PARAM_UNUSED ) {
555 0 : if( FD_UNLIKELY( in_idx==ctx->adopt_in_idx ) ) {
556 0 : if( FD_UNLIKELY( chunk<ctx->adopt_in_chunk0 || chunk>ctx->adopt_in_wmark || sz!=sizeof(fd_votor_adopt_result_t) ) ) {
557 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->adopt_in_chunk0, ctx->adopt_in_wmark ));
558 0 : }
559 0 : fd_memcpy( &ctx->adopt_result, fd_chunk_to_laddr_const( ctx->adopt_in_mem, chunk ), sizeof(fd_votor_adopt_result_t) );
560 0 : return;
561 0 : }
562 0 : if( FD_UNLIKELY( in_idx==ctx->admin_in_idx ) ) {
563 0 : if( FD_UNLIKELY( chunk<ctx->admin_in_chunk0 || chunk>ctx->admin_in_wmark || sz!=sizeof(fd_failover_bus_msg_t) ) ) {
564 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->admin_in_chunk0, ctx->admin_in_wmark ));
565 0 : }
566 0 : fd_failover_bus_msg_t const * msg = fd_chunk_to_laddr_const( ctx->admin_in_mem, chunk );
567 0 : if( FD_UNLIKELY( sig==FD_FAILOVER_BUS_SWITCH_RESP ) ) {
568 0 : fd_memcpy( &ctx->id_switch.response, msg->payload, sizeof(ctx->id_switch.response) );
569 0 : ctx->id_switch.response_nonce = msg->nonce;
570 0 : return;
571 0 : }
572 0 : if( FD_UNLIKELY( sig==FD_FAILOVER_BUS_OPERATOR_ACTION ) ) {
573 0 : fd_memcpy( &ctx->bus_operator, msg->payload, sizeof(ctx->bus_operator) );
574 0 : return;
575 0 : }
576 0 : fd_memcpy( &ctx->bus_req, msg, sizeof(fd_failover_bus_msg_t) );
577 0 : return;
578 0 : }
579 0 : if( FD_UNLIKELY( in_idx==ctx->hist_in_idx ) ) {
580 0 : ulong frame_sz = sizeof(fd_votor_hist_msg_t);
581 0 : if( FD_UNLIKELY( chunk<ctx->hist_in_chunk0 || chunk>ctx->hist_in_wmark || sz!=frame_sz ) ) {
582 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->hist_in_chunk0, ctx->hist_in_wmark ));
583 0 : }
584 0 : fd_memcpy( &ctx->hist, fd_chunk_to_laddr_const( ctx->hist_in_mem, chunk ), sizeof(fd_votor_hist_msg_t) );
585 0 : return;
586 0 : }
587 0 : if( FD_UNLIKELY( in_idx!=ctx->gossip_in_idx ) ) return;
588 0 : if( FD_UNLIKELY( chunk<ctx->gossip_in_chunk0 || chunk>ctx->gossip_in_wmark || sz>ctx->gossip_in_mtu ) ) {
589 0 : FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->gossip_in_chunk0, ctx->gossip_in_wmark ));
590 0 : }
591 : /* Copy here and commit in after_frag. */
592 0 : fd_gossip_update_message_t const * msg = fd_chunk_to_laddr_const( ctx->gossip_in_mem, chunk );
593 0 : fd_memcpy( ctx->gossip_origin, msg->origin, 32UL );
594 0 : ctx->gossip_socket.addr = 0U;
595 0 : ctx->gossip_socket.port = (ushort)0;
596 0 : if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO ) ) {
597 0 : fd_gossip_socket_t const * socket = &msg->contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ];
598 0 : ctx->gossip_socket.addr = fd_uint_if ( !socket->is_ipv6, socket->ip4, 0U );
599 0 : ctx->gossip_socket.port = fd_ushort_if( !socket->is_ipv6, socket->port, (ushort)0 );
600 0 : }
601 0 : }
602 :
603 : /* Unknown adoption results are invalid. */
604 : static ulong
605 0 : validate_adopt_code( ulong result ) {
606 0 : return result<FD_VOTOR_ADOPT_RESULT_CNT ? result : FD_VOTOR_ADOPT_ERR_INVALID;
607 0 : }
608 :
609 : static void
610 : after_frag( fd_failover_tile_ctx_t * ctx,
611 : ulong in_idx,
612 : ulong seq,
613 : ulong sig,
614 : ulong sz FD_PARAM_UNUSED,
615 : ulong tsorig FD_PARAM_UNUSED,
616 : ulong tspub FD_PARAM_UNUSED,
617 0 : fd_stem_context_t * stem FD_PARAM_UNUSED ) {
618 0 : if( FD_UNLIKELY( in_idx==ctx->adopt_in_idx ) ) {
619 0 : ctx->adopt_result.result = validate_adopt_code( ctx->adopt_result.result );
620 0 : ctx->adopt_result_id = sig;
621 0 : ctx->adopt_result_fresh = 1;
622 0 : return;
623 0 : }
624 0 : if( FD_UNLIKELY( in_idx==ctx->admin_in_idx ) ) {
625 0 : if( FD_UNLIKELY( sig==FD_FAILOVER_BUS_SWITCH_RESP ) ) {
626 : /* The drain still reads the last switch result, so we copy a
627 : response into it only after the stem and nonce checks pass. */
628 0 : if( FD_LIKELY( on_switch_resp_msg( ctx, ctx->id_switch.response_nonce ) ) ) ctx->id_switch.result = ctx->id_switch.response;
629 0 : if( FD_UNLIKELY( ctx->id_switch.response.result==FD_FAILOVER_SWITCH_ERR_STALE ) ) {
630 0 : on_admin_operator_action_msg( ctx, &ctx->id_switch.response.operator );
631 0 : }
632 0 : return;
633 0 : }
634 0 : if( FD_UNLIKELY( sig==FD_FAILOVER_BUS_OPERATOR_ACTION ) ) {
635 0 : on_admin_operator_action_msg( ctx, &ctx->bus_operator );
636 0 : return;
637 0 : }
638 : /* Send the response from after_credit, where a publish credit is available. */
639 0 : ctx->bus_req_fresh = 1;
640 0 : return;
641 0 : }
642 0 : if( FD_UNLIKELY( in_idx==ctx->hist_in_idx ) ) {
643 0 : ctx->hist_fresh = 1;
644 0 : ctx->hist_seen_seq = seq;
645 0 : return;
646 0 : }
647 0 : if( FD_UNLIKELY( in_idx!=ctx->gossip_in_idx ) ) return;
648 0 : on_gossip_contact_info_msg( ctx, fd_failover_clock() );
649 0 : }
650 :
651 : static inline void
652 : after_credit( fd_failover_tile_ctx_t * ctx,
653 : fd_stem_context_t * stem,
654 : int * opt_poll_in FD_PARAM_UNUSED,
655 0 : int * charge_busy ) {
656 0 : if( FD_UNLIKELY( ctx->hist_fresh ) ) {
657 0 : ctx->hist_fresh = 0;
658 0 : on_votor_hist_msg( ctx, &ctx->hist );
659 0 : }
660 0 : step_controller( ctx, stem );
661 :
662 0 : long now = fd_failover_clock();
663 0 : if( FD_LIKELY( stem->now>=ctx->next_wake ) ) {
664 0 : int busy = 0;
665 0 : poll_peer( ctx, now, &busy );
666 0 : long poll_nanos = ctx->action!=FD_FAILOVER_ACTION_IDLE ? FD_FAILOVER_TILE_POLL_NANOS : FD_FAILOVER_TILE_PARK_NANOS;
667 0 : ctx->next_wake = busy ? 0L : stem->now+(long)((double)poll_nanos*fd_tempo_tick_per_ns( NULL ));
668 0 : if( busy ) *charge_busy = 1;
669 0 : }
670 0 : flush_pending( ctx, now );
671 0 : if( FD_UNLIKELY( ctx->cmd_nonce && ctx->cmd_step_tail!=ctx->cmd_step_head ) ) {
672 0 : if( stem->cr_avail[ ctx->admin_out_idx ]>=stem->depths[ ctx->admin_out_idx ] ) {
673 0 : publish_cmd_step( ctx, stem );
674 0 : *charge_busy = 1;
675 0 : }
676 0 : } else if( FD_UNLIKELY( ctx->cmd_done ) ) {
677 0 : publish_cmd_answer( ctx, stem );
678 0 : *charge_busy = 1;
679 0 : }
680 0 : if( FD_UNLIKELY( ctx->bus_req_fresh ) ) {
681 0 : ctx->bus_req_fresh = 0;
682 0 : serve_bus_request( ctx, stem, now );
683 0 : *charge_busy = 1;
684 0 : }
685 :
686 0 : if( FD_UNLIKELY( ctx->stuck!=ctx->stuck_logged ) ) {
687 0 : ctx->stuck_logged = ctx->stuck;
688 0 : if( !ctx->stuck ) FD_LOG_NOTICE(( "failover is no longer stuck, %s", holds_identity( ctx ) ? "this machine runs the identity" : "this machine does not run the identity" ));
689 0 : }
690 0 : }
691 :
692 : static inline long
693 0 : next_deadline( fd_failover_tile_ctx_t const * ctx ) {
694 0 : return ctx->next_wake;
695 0 : }
696 :
697 : static ulong
698 : populate_allowed_seccomp( fd_topo_t const * topo,
699 : fd_topo_tile_t const * tile,
700 : ulong out_cnt,
701 0 : struct sock_filter * out ) {
702 0 : fd_failover_tile_ctx_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
703 0 : populate_sock_filter_policy_fd_failover_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)fd_failover_channel_listen_fd( ctx->channel ) );
704 0 : return sock_filter_policy_fd_failover_tile_instr_cnt;
705 0 : }
706 :
707 : static ulong
708 : rlimit_file_cnt( fd_topo_t const * topo FD_PARAM_UNUSED,
709 0 : fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
710 0 : return FD_FAILOVER_CHANNEL_CANDIDATE_MAX + 1UL + 8UL;
711 0 : }
712 :
713 : static ulong
714 : populate_allowed_fds( fd_topo_t const * topo,
715 : fd_topo_tile_t const * tile,
716 : ulong out_fds_cnt,
717 0 : int * out_fds ) {
718 0 : fd_failover_tile_ctx_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
719 0 : int logfile_fd = fd_log_private_logfile_fd();
720 0 : int listen_fd = fd_failover_channel_listen_fd( ctx->channel );
721 :
722 0 : ulong required_fds = 1UL + (ulong)(-1!=logfile_fd) + (ulong)(-1!=listen_fd);
723 0 : if( FD_UNLIKELY( out_fds_cnt<required_fds ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
724 :
725 0 : ulong out_cnt = 0UL;
726 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
727 0 : if( FD_LIKELY( -1!=logfile_fd ) ) out_fds[ out_cnt++ ] = logfile_fd; /* logfile */
728 0 : if( FD_UNLIKELY( -1!=listen_fd ) ) out_fds[ out_cnt++ ] = listen_fd; /* failover listener */
729 0 : return out_cnt;
730 0 : }
731 :
732 0 : #define STEM_BURST (3UL)
733 0 : #define STEM_LAZY ((long)1e6) /* 1ms */
734 :
735 : #define STEM_ALWAYS_PARK 1
736 :
737 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_failover_tile_ctx_t
738 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_failover_tile_ctx_t)
739 :
740 0 : #define STEM_CALLBACK_NEXT_DEADLINE next_deadline
741 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
742 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
743 0 : #define STEM_CALLBACK_DURING_FRAG during_frag
744 0 : #define STEM_CALLBACK_AFTER_FRAG after_frag
745 :
746 : #include "../../disco/stem/fd_stem.c"
747 :
748 : /* A serialized history has to fit within the DEMOTED payload. */
749 : FD_STATIC_ASSERT( FD_VOTOR_HIST_SER_MAX<=FD_FAILOVER_ALPENGLOW_STATE_MAX, alpenglow_state_max );
750 :
751 : fd_topo_run_tile_t fd_tile_failov = {
752 : .name = "failov",
753 : .keep_host_networking = 1,
754 : .allow_connect = 1,
755 : .rlimit_file_cnt_fn = rlimit_file_cnt,
756 : .populate_allowed_seccomp = populate_allowed_seccomp,
757 : .populate_allowed_fds = populate_allowed_fds,
758 : .scratch_align = scratch_align,
759 : .scratch_footprint = scratch_footprint,
760 : .privileged_init = privileged_init,
761 : .unprivileged_init = unprivileged_init,
762 : .run = stem_run,
763 : };
|