Line data Source code
1 : #include "fd_keyguard.h"
2 : #include "fd_keyguard_client.h"
3 : #include "../bundle/fd_bundle_crank_constants.h"
4 : #include "../../flamenco/runtime/fd_system_ids.h"
5 : #include "../../flamenco/gossip/fd_gossip_value.h"
6 : #include "../../ballet/txn/fd_compact_u16.h"
7 : #include "../../waltz/tls/fd_tls.h"
8 : /* manually include just fd_features_generated.h so we can get
9 : FD_FEATURE_SET_ID without anything else that we don't need. */
10 : #define HEADER_fd_src_flamenco_features_fd_features_h
11 : #include "../../flamenco/features/fd_features_generated.h"
12 : #undef HEADER_fd_src_flamenco_features_fd_features_h
13 :
14 : struct fd_keyguard_sign_req {
15 : fd_keyguard_authority_t * authority;
16 : };
17 :
18 : typedef struct fd_keyguard_sign_req fd_keyguard_sign_req_t;
19 :
20 : static int
21 : fd_keyguard_authorize_vote_txn( fd_keyguard_authority_t const * authority,
22 : uchar const * data,
23 : ulong sz,
24 3 : int sign_type ) {
25 3 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
26 3 : if( sz > FD_TXN_MTU_V0 ) return 0;
27 : /* Each vote transaction may have 1 or 2 signers. The first byte in
28 : the transaction message is the number of signers. */
29 3 : ulong off = 0UL;
30 3 : uchar signer_cnt = data[off];
31 3 : if( signer_cnt!=1 && signer_cnt!=2 ) return 0;
32 3 : if( signer_cnt==1 && sz<=140 ) return 0;
33 3 : if( signer_cnt==2 && sz<=172 ) return 0;
34 : /* The authority's public key will be the first listed account in the
35 : transaction message. */
36 :
37 : /* r/o signers = 1 when there are 2 signers and 1 otherwise. */
38 0 : off++;
39 0 : if( data[off]!=signer_cnt-1 ) return 0;
40 :
41 : /* There will always be 1 r/o unsigned account. */
42 0 : off++;
43 0 : if( data[off]!=1 ) return 0;
44 :
45 : /* The only accounts should be the 1 or 2 signers, the vote account,
46 : and the vote program. The number of accounts is represented as a
47 : compact u16. */
48 0 : off++;
49 0 : ulong bytes = fd_cu16_dec_sz( data+off, 3UL );
50 0 : if( bytes!=1UL ) return 0;
51 0 : ulong acc_cnt = 2UL + signer_cnt;
52 0 : if( data[off]!=acc_cnt ) return 0;
53 :
54 : /* The first account should always be the authority's public key. */
55 0 : off++;
56 0 : ulong acct_off = off;
57 0 : if( memcmp( authority->identity_pubkey, data + acct_off, 32 ) ) return 0;
58 :
59 : /* Each transaction account key is listed out and is followed by a 32
60 : byte blockhash. The instruction count is after this. */
61 0 : off += (acc_cnt+1) * 32;
62 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
63 0 : uchar instr_cnt = data[ off ];
64 0 : if( bytes!=1UL ) return 0;
65 0 : if( instr_cnt!=1 ) return 0;
66 :
67 : /* The program id will be the first byte of the instruction payload
68 : and should be the vote program. */
69 0 : off++;
70 0 : uchar program_id = data[ off ];
71 0 : if( program_id != acc_cnt-1 ) return 0;
72 0 : ulong program_acct_off = 4UL + (program_id * 32UL);
73 0 : if( memcmp( &fd_solana_vote_program_id, data+program_acct_off, 32 ) ) return 0;
74 :
75 0 : off++;
76 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
77 0 : if( bytes!=1UL ) return 0;
78 :
79 : /* Vote account count will always be 2. One byte is used to list the
80 : account count for the transaction and 1 byte for each account. */
81 0 : if( data[ off ]!=2 ) return 0;
82 0 : off += 3UL;
83 :
84 : /* Move the cursor forward by the instruction data size. The first
85 : byte of the instruction data will be the discriminant. Only allow
86 : tower sync vote instructions (14). */
87 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
88 0 : off += bytes;
89 0 : if( data[off]!=14 ) return 0;
90 :
91 0 : return 1;
92 0 : }
93 :
94 : static int
95 : fd_keyguard_authorize_gossip( fd_keyguard_authority_t const * authority,
96 : uchar const * data,
97 : ulong sz,
98 0 : int sign_type ) {
99 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
100 :
101 : /* Every gossip message contains a 4 byte enum variant tag (at the
102 : beginning of the message) and a 32 byte public key (at an arbitrary
103 : location). */
104 0 : if( sz<36UL ) return 0;
105 0 : if( sz>1188UL-64UL ) return 0;
106 :
107 0 : uint tag = FD_LOAD( uint, data );
108 0 : ulong origin_off = ULONG_MAX;
109 0 : switch( tag ) {
110 0 : case FD_GOSSIP_VALUE_VOTE:
111 0 : origin_off = 1UL;
112 0 : if( sz<4UL+1UL+32UL+FD_TXN_MIN_SERIALIZED_SZ+8UL ) return 0;
113 0 : ulong sig_cnt = data[ 4UL+1UL+32UL ];
114 0 : if( (sig_cnt==0UL) | (sig_cnt>2UL) ) return 0;
115 0 : ulong vote_off = 4UL+1UL+32UL+1UL+64UL*sig_cnt;
116 0 : if( !fd_keyguard_authorize_vote_txn( authority, data+vote_off, sz-(vote_off+8UL), FD_KEYGUARD_SIGN_TYPE_ED25519 ) )
117 0 : return 0;
118 0 : break;
119 0 : case FD_GOSSIP_VALUE_CONTACT_INFO:
120 0 : origin_off = 0UL;
121 : /* Contact info is pretty tough to parse. The best we can do is
122 : check the feature set and client ID.
123 : min sz
124 : 4B tag
125 : 32B origin
126 : 1B-10B wallclock
127 : 8B outset
128 : 2B shred version
129 : 1B-3B major version
130 : 1B-3B minor version
131 : 1B-3B patch version
132 : 4B commit
133 : 4B feature set
134 : 1B-3B client ID
135 : 1B-3B unique addr cnt
136 : ??? IP addresses
137 : 1B-3B socket cnt
138 : ??? ports
139 : 1B-3B extension len
140 : ...
141 :
142 : Total: 62B+
143 : */
144 0 : if( sz<62UL ) return 0;
145 0 : ulong off = 4UL+32UL;
146 0 : for( ulong i=0UL; i<10UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
147 0 : off += 8UL+2UL;
148 : /* off<56, so we're still safe here */
149 0 : if( sz<off+15UL ) return 0;
150 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
151 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
152 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
153 0 : if( sz<off+12UL ) return 0;
154 0 : uint commit = FD_LOAD( uint, data+off ); off += 4UL;
155 0 : uint feature_set = FD_LOAD( uint, data+off ); off += 4UL;
156 0 : uchar client_id = data[ off ];
157 0 : (void)commit; /* Checking commit introduces a circular dependency between disco and app :'( */
158 0 : if( feature_set!=FD_FEATURE_SET_ID ) return 0;
159 0 : if( client_id !=5 ) return 0; /* FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER */
160 :
161 0 : break;
162 0 : case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
163 0 : origin_off = 2UL;
164 0 : if( sz< 4UL+65UL ) return 0;
165 0 : ulong chunk_len = FD_LOAD( ulong, data+4UL+57UL );
166 0 : if( sz!=4UL+65UL+chunk_len ) return 0;
167 0 : break;
168 :
169 : /* We don't sign these yet. */
170 0 : case FD_GOSSIP_VALUE_NODE_INSTANCE: /* origin_off = 0UL; break; */ return 0;
171 0 : case FD_GOSSIP_VALUE_SNAPSHOT_HASHES: /* origin_off = 0UL; break; */ return 0;
172 :
173 : /* We refuse to serialize these */
174 0 : case FD_GOSSIP_VALUE_LEGACY_CONTACT_INFO:
175 0 : case FD_GOSSIP_VALUE_LOWEST_SLOT:
176 0 : case FD_GOSSIP_VALUE_LEGACY_SNAPSHOT_HASHES:
177 0 : case FD_GOSSIP_VALUE_ACCOUNT_HASHES:
178 0 : case FD_GOSSIP_VALUE_EPOCH_SLOTS:
179 0 : case FD_GOSSIP_VALUE_LEGACY_VERSION:
180 0 : case FD_GOSSIP_VALUE_VERSION:
181 0 : case FD_GOSSIP_VALUE_RESTART_LAST_VOTED_FORK_SLOTS:
182 0 : case FD_GOSSIP_VALUE_RESTART_HEAVIEST_FORK:
183 0 : default:
184 0 : return 0;
185 0 : }
186 0 : if( sz<sizeof(uint)+origin_off+32UL ) return 0;
187 :
188 0 : return fd_memeq( authority->identity_pubkey, data+sizeof(uint)+origin_off, 32UL );
189 0 : }
190 :
191 : static int
192 : fd_keyguard_authorize_bundle_crank_txn( fd_keyguard_authority_t const * authority,
193 : uchar const * data,
194 : ulong sz,
195 0 : int sign_type ) {
196 :
197 0 : static const uchar crank_2_mask[ FD_BUNDLE_CRANK_2_SZ ] = { FD_BUNDLE_CRANK_2_MASK };
198 0 : static const uchar crank_2_vals[ FD_BUNDLE_CRANK_2_SZ ] = { FD_BUNDLE_CRANK_2_VALS };
199 0 : static const uchar crank_3_mask[ FD_BUNDLE_CRANK_3_SZ ] = { FD_BUNDLE_CRANK_3_MASK };
200 0 : static const uchar crank_3_vals[ FD_BUNDLE_CRANK_3_SZ ] = { FD_BUNDLE_CRANK_3_VALS };
201 :
202 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
203 :
204 0 : int matches_mask = 1;
205 0 : switch( sz ) {
206 0 : case (FD_BUNDLE_CRANK_2_SZ-65UL):
207 0 : for( ulong i=0UL; i<FD_BUNDLE_CRANK_2_SZ-65UL; i++ ) {
208 0 : if( crank_2_mask[i+65UL] ) matches_mask &= data[i]==crank_2_vals[i+65UL];
209 0 : }
210 0 : break;
211 0 : case (FD_BUNDLE_CRANK_3_SZ-65UL):
212 0 : for( ulong i=0UL; i<FD_BUNDLE_CRANK_3_SZ-65UL; i++ ) {
213 0 : if( crank_3_mask[i+65UL] ) matches_mask &= data[i]==crank_3_vals[i+65UL];
214 0 : }
215 0 : if( !fd_memeq( data+FD_BUNDLE_CRANK_TIP_DISTRIBUTION_OFFSET-65UL, authority->tip_distribution_program, 32UL ) ) return 0;
216 0 : break;
217 0 : default:
218 0 : return 0;
219 0 : }
220 0 : if( !matches_mask ) return 0;
221 :
222 0 : if( !fd_memeq( data+FD_BUNDLE_CRANK_SIGNER_OFFSET -65UL, authority->identity_pubkey, 32UL ) ) return 0;
223 0 : if( !fd_memeq( data+FD_BUNDLE_CRANK_TIP_PAYMENT_OFFSET-65UL, authority->tip_payment_program, 32UL ) ) return 0;
224 :
225 0 : return 1;
226 0 : }
227 :
228 : static int
229 : fd_keyguard_authorize_ping( fd_keyguard_authority_t const * authority,
230 : uchar const * data,
231 : ulong sz,
232 0 : int sign_type ) {
233 0 : (void)authority;
234 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
235 0 : if( sz != 32 ) return 0;
236 0 : if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
237 0 : return 1;
238 0 : }
239 :
240 : static int
241 : fd_keyguard_authorize_pong( fd_keyguard_authority_t const * authority,
242 : uchar const * data,
243 : ulong sz,
244 0 : int sign_type ) {
245 0 : (void)authority;
246 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519 ) return 0;
247 0 : if( sz != 48 ) return 0;
248 0 : if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
249 0 : return 1;
250 0 : }
251 :
252 : static int
253 : fd_keyguard_authorize_gossip_prune( fd_keyguard_authority_t const * authority,
254 : uchar const * data,
255 : ulong sz,
256 0 : int sign_type ) {
257 0 : if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
258 : /* Prune messages always start with the prefix followed by the pubkey. */
259 0 : if( sz<66UL ) return 0;
260 0 : if( FD_LOAD( ulong, data )!=18UL ) return 0;
261 0 : if( 0!=memcmp( data+8UL, "\xffSOLANA_PRUNE_DATA", 18 ) ) return 0;
262 0 : if( 0!=memcmp( authority->identity_pubkey, data+26UL, 32 ) ) return 0;
263 0 : return 1;
264 0 : }
265 :
266 : static int
267 : fd_keyguard_authorize_repair( fd_keyguard_authority_t const * authority,
268 : uchar const * data,
269 : ulong sz,
270 0 : int sign_type ) {
271 :
272 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
273 0 : if( sz<80 ) return 0;
274 :
275 0 : uint discriminant = fd_uint_load_4( data );
276 0 : uchar const * sender = data+4;
277 :
278 0 : if( discriminant< 8 ) return 0; /* window_index is min ID */
279 0 : if( discriminant>14 ) return 0; /* shred_for_block_id is max ID */
280 :
281 0 : if( 0!=memcmp( authority->identity_pubkey, sender, 32 ) ) return 0;
282 :
283 0 : return 1;
284 0 : }
285 :
286 : static int
287 : fd_keyguard_authorize_tls_cv( fd_keyguard_authority_t const * authority FD_PARAM_UNUSED,
288 : uchar const * data,
289 : ulong sz,
290 0 : int sign_type ) {
291 0 : if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
292 0 : if( FD_UNLIKELY( sz != 130 ) ) return 0;
293 :
294 : /* validate client prefix against fd_tls */
295 0 : return fd_memeq( fd_tls13_cli_sign_prefix, data, sizeof(fd_tls13_cli_sign_prefix) );
296 0 : }
297 :
298 : static int
299 : fd_keyguard_authorize_tls_cv_srv( fd_keyguard_authority_t const * authority FD_PARAM_UNUSED,
300 : uchar const * data,
301 : ulong sz,
302 0 : int sign_type ) {
303 0 : if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
304 0 : if( FD_UNLIKELY( sz != 130 ) ) return 0;
305 :
306 : /* validate server prefix against fd_tls */
307 0 : return fd_memeq( fd_tls13_srv_sign_prefix, data, sizeof(fd_tls13_srv_sign_prefix) );
308 0 : }
309 :
310 : static int
311 : fd_keyguard_authorize_ag_vote( fd_keyguard_authority_t const * authority FD_PARAM_UNUSED,
312 : uchar const * data FD_PARAM_UNUSED,
313 : ulong sz FD_PARAM_UNUSED,
314 6 : int sign_type ) {
315 6 : return sign_type==FD_KEYGUARD_SIGN_TYPE_BLS;
316 6 : }
317 :
318 : int
319 : fd_keyguard_payload_authorize( fd_keyguard_authority_t const * authority,
320 : uchar const * data,
321 : ulong sz,
322 : int role,
323 30 : int sign_type ) {
324 :
325 30 : if( sz > FD_KEYGUARD_SIGN_REQ_MTU ) {
326 0 : FD_LOG_WARNING(( "oversz signing request (role=%d sz=%lu)", role, sz ));
327 0 : return 0;
328 0 : }
329 :
330 : /* Identify payload type */
331 :
332 30 : ulong payload_mask = fd_keyguard_payload_match( data, sz, sign_type );
333 30 : int match_cnt = fd_ulong_popcnt( payload_mask );
334 30 : if( FD_UNLIKELY( payload_mask==0UL ) ) {
335 15 : FD_LOG_WARNING(( "unrecognized payload type (role=%#x)", (uint)role ));
336 15 : }
337 :
338 30 : int is_ambiguous = match_cnt != 1;
339 :
340 : /* We know that gossip, repair request/response message are
341 : ambiguous, so allow them to collide here. */
342 30 : int is_gossip_repair =
343 30 : 0==( payload_mask &
344 30 : (~( FD_KEYGUARD_PAYLOAD_GOSSIP |
345 30 : FD_KEYGUARD_PAYLOAD_REPAIR ) ) );
346 : /* Also allow ambiguities between shred and gossip ping messages
347 : until shred sign type is fixed... */
348 30 : int is_shred_ping =
349 30 : 0==( payload_mask &
350 30 : (~( FD_KEYGUARD_PAYLOAD_SHRED |
351 30 : FD_KEYGUARD_PAYLOAD_PING ) ) );
352 :
353 30 : if( FD_UNLIKELY( is_ambiguous && !is_gossip_repair && !is_shred_ping ) ) {
354 0 : FD_LOG_WARNING(( "ambiguous payload type (role=%#x mask=%#lx)", (uint)role, payload_mask ));
355 0 : }
356 :
357 : /* Authorize each role */
358 :
359 30 : switch( role ) {
360 :
361 6 : case FD_KEYGUARD_ROLE_TXSEND: {
362 6 : int txn_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TXN )) &&
363 6 : fd_keyguard_authorize_vote_txn( authority, data, sz, sign_type );
364 6 : int tls_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TLS_CV )) &&
365 6 : fd_keyguard_authorize_tls_cv( authority, data, sz, sign_type );
366 6 : if( FD_UNLIKELY( !txn_ok && !tls_ok ) ) {
367 6 : FD_LOG_WARNING(( "unauthorized payload type for send (mask=%#lx)", payload_mask ));
368 6 : return 0;
369 6 : }
370 0 : return 1;
371 6 : }
372 :
373 3 : case FD_KEYGUARD_ROLE_GOSSIP: {
374 3 : int ping_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
375 3 : fd_keyguard_authorize_ping( authority, data, sz, sign_type );
376 3 : int pong_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
377 3 : fd_keyguard_authorize_pong( authority, data, sz, sign_type );
378 3 : int prune_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PRUNE )) &&
379 3 : fd_keyguard_authorize_gossip_prune( authority, data, sz, sign_type );
380 3 : int gossip_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_GOSSIP )) &&
381 3 : fd_keyguard_authorize_gossip( authority, data, sz, sign_type );
382 3 : if( FD_UNLIKELY( !ping_ok && !pong_ok && !prune_ok && !gossip_ok ) ) {
383 3 : FD_LOG_WARNING(( "unauthorized payload type for gossip (mask=%#lx)", payload_mask ));
384 3 : return 0;
385 3 : }
386 0 : return 1;
387 3 : }
388 :
389 0 : case FD_KEYGUARD_ROLE_REPAIR: {
390 0 : int pong_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
391 0 : fd_keyguard_authorize_pong( authority, data, sz, sign_type );
392 0 : int repair_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_REPAIR )) &&
393 0 : fd_keyguard_authorize_repair( authority, data, sz, sign_type );
394 0 : if( FD_UNLIKELY( !pong_ok && !repair_ok ) ) {
395 0 : FD_LOG_WARNING(( "unauthorized payload type for repair (mask=%#lx)", payload_mask ));
396 0 : return 0;
397 0 : }
398 0 : return 1;
399 0 : }
400 :
401 0 : case FD_KEYGUARD_ROLE_LEADER:
402 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_SHRED ) ) {
403 0 : FD_LOG_WARNING(( "unauthorized payload type for leader (mask=%#lx)", payload_mask ));
404 0 : return 0;
405 0 : }
406 : /* no further restrictions on shred */
407 0 : return 1;
408 :
409 0 : case FD_KEYGUARD_ROLE_BUNDLE:
410 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_BUNDLE ) ) {
411 0 : FD_LOG_WARNING(( "unauthorized payload type for bundle (mask=%#lx)", payload_mask ));
412 0 : return 0;
413 0 : }
414 : /* no further restrictions on bundle */
415 0 : return 1;
416 :
417 0 : case FD_KEYGUARD_ROLE_EVENT:
418 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_EVENT ) ) {
419 0 : FD_LOG_WARNING(( "unauthorized payload type for event (mask=%#lx)", payload_mask ));
420 0 : return 0;
421 0 : }
422 : /* no further restrictions on event */
423 0 : return 1;
424 :
425 0 : case FD_KEYGUARD_ROLE_BUNDLE_CRANK:
426 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_TXN ) ) {
427 0 : FD_LOG_WARNING(( "unauthorized payload type for crank bundle (mask=%#lx)", payload_mask ));
428 0 : return 0;
429 0 : }
430 0 : return fd_keyguard_authorize_bundle_crank_txn( authority, data, sz, sign_type );
431 :
432 0 : case FD_KEYGUARD_ROLE_RSERVE: {
433 0 : int rserve_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
434 0 : fd_keyguard_authorize_ping( authority, data, sz, sign_type );
435 0 : if( FD_UNLIKELY( !rserve_ok ) ) {
436 0 : FD_LOG_WARNING(( "unauthorized payload type for rserve (mask=%#lx)", payload_mask ));
437 0 : return 0;
438 0 : }
439 0 : return 1;
440 0 : }
441 :
442 21 : case FD_KEYGUARD_ROLE_VOTOR: {
443 21 : int tls_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TLS_CV )) &&
444 21 : ( fd_keyguard_authorize_tls_cv ( authority, data, sz, sign_type ) ||
445 0 : fd_keyguard_authorize_tls_cv_srv( authority, data, sz, sign_type ) );
446 21 : int vote_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_AG_VOTE )) &&
447 21 : fd_keyguard_authorize_ag_vote( authority, data, sz, sign_type );
448 21 : if( FD_UNLIKELY( !tls_ok && !vote_ok ) ) {
449 15 : FD_LOG_WARNING(( "unauthorized payload type for votor (mask=%#lx)", payload_mask ));
450 15 : return 0;
451 15 : }
452 6 : return 1;
453 21 : }
454 :
455 0 : default:
456 0 : FD_LOG_WARNING(( "unsupported role=%#x", (uint)role ));
457 0 : return 0;
458 30 : }
459 30 : }
|